Skip to content
ver .md original

Mostre um quadro e mantenha-o atualizado

Renderize um quadro do Dailybot Plan em uma requisição e mantenha-o em dia com o feed de mudanças: ETag e 304, consultas periódicas no ritmo do servidor, truncamento e expiração do cursor.

Beta

Plan está em beta. Tudo o que está em /plan no aplicativo web, os comandos da CLI e da agent skill para projetos, metas, quadros e tarefas, e a API pública /v1/plan/ podem mudar antes da disponibilidade geral. Quer testar com sua equipe? Escreva para [email protected].

Uma tela de quadro precisa de duas coisas: o quadro inteiro uma vez e, depois, apenas o que mudou. A API do Plan entrega exatamente isso: um snapshot que renderiza todas as colunas em uma requisição e um feed de mudanças que retorna as tarefas alteradas desde um cursor. Consulte o feed; nunca leia o quadro inteiro de novo em intervalos fixos.

1. Leia o quadro uma vez

curl -sS -D headers.txt "https://api.dailybot.com/v1/plan/boards/$BOARD/board/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN" > board.json

A resposta tem uma entrada em groups por coluna, na ordem das colunas. Cada grupo traz o task_count real da coluna, suas primeiras tarefas por ordem de rank e has_more quando a coluna tem mais cartões do que cabem na página (até 50 por coluna, definido com tasks_per_state). Guarde duas coisas:

  • delta_cursor do corpo: onde o feed de mudanças começa.
  • O header ETag: envie-o de volta como If-None-Match para receber 304 Not Modified quando nada tiver mudado.

Para paginar o restante de uma coluna longa, use a lista de tarefas com o mesmo quadro e o mesmo estado: GET /v1/plan/tasks/?board=$BOARD&state=<state uuid>.

2. Peça apenas o que mudou

curl -sS "https://api.dailybot.com/v1/plan/boards/$BOARD/delta/?updated_since=$CURSOR" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN"
{
  "since": "2026-09-25T10:14:02.113954Z",
  "cursor": "2026-09-25T10:19:44.902311Z",
  "changed": [ { "uuid": "00000000-0000-4000-8000-000000000005", "key": "ENG-142", "state": { "name": "Done", "category": "done" }, "version": 9 } ],
  "removed": [ { "uuid": "00000000-0000-4000-8000-000000000105", "key": "ENG-77", "reason": "archived" } ],
  "states": null,
  "truncated": false,
  "poll_after_seconds": 15
}

Aplique a resposta nesta ordem:

  1. states: quando não for null, uma coluna foi criada, renomeada, reordenada ou arquivada: substitua toda a sua lista de colunas por ela.
  2. changed: faça upsert de cada tarefa por uuid (uma entrada por tarefa; a versão atual prevalece).
  3. removed: retire cada tarefa do quadro.
  4. Guarde o cursor e envie-o exatamente como veio em updated_since na próxima chamada. Nunca calcule um cursor com base no seu próprio relógio.

A entrega é at-least-once: uma tarefa alterada no mesmo instante do seu cursor pode chegar duas vezes, então os upserts precisam ser idempotentes.

3. Deixe o servidor definir o ritmo

  • Espere poll_after_seconds antes da próxima chamada. O valor começa em 15 segundos, dobra até 120 enquanto o quadro está parado e volta a cair assim que algo muda.
  • Se truncated for true, há mais mudanças esperando: consulte de novo imediatamente.
  • Pause enquanto a página estiver oculta e consulte uma vez quando ela voltar a ficar visível.
  • Um cursor com mais de 7 dias retorna 400 delta_window_expired: leia o snapshot de novo e recomece a partir do delta_cursor dele.
  • O feed de mudanças permite 240 chamadas por minuto por ator. Seguir poll_after_seconds mantém você bem abaixo desse limite.

Um loop de consultas completo

Um loop em shell para um script ou um dashboard no terminal:

CURSOR=$(jq -r .delta_cursor board.json)
while true; do
  RESPONSE=$(curl -sS "https://api.dailybot.com/v1/plan/boards/$BOARD/delta/?updated_since=$CURSOR" \
    -H "Authorization: Bearer $DAILYBOT_TOKEN")
  if [ "$(echo "$RESPONSE" | jq -r '.code // empty')" = "delta_window_expired" ]; then
    echo "Cursor expired: read the snapshot again"; break
  fi
  echo "$RESPONSE" | jq -c '{changed: [.changed[].key], removed: [.removed[].key]}'
  CURSOR=$(echo "$RESPONSE" | jq -r .cursor)
  if [ "$(echo "$RESPONSE" | jq -r .truncated)" = "true" ]; then continue; fi
  sleep "$(echo "$RESPONSE" | jq -r .poll_after_seconds)"
done

O mesmo loop em um serviço Node.js (Node 18 ou posterior), com o token guardado no servidor:

const API = 'https://api.dailybot.com/v1/plan';
const headers = { Authorization: `Bearer ${process.env.DAILYBOT_TOKEN}` };
const sleep = (s) => new Promise((resolve) => setTimeout(resolve, s * 1000));

async function follow(boardId, onChange) {
  let res = await fetch(`${API}/boards/${boardId}/board/`, { headers });
  let cursor = (await res.json()).delta_cursor;
  for (;;) {
    res = await fetch(`${API}/boards/${boardId}/delta/?updated_since=${encodeURIComponent(cursor)}`, { headers });
    const delta = await res.json();
    if (delta.code === 'delta_window_expired') return follow(boardId, onChange);
    onChange(delta); // replace columns if delta.states, upsert delta.changed, drop delta.removed
    cursor = delta.cursor;
    if (!delta.truncated) await sleep(delta.poll_after_seconds);
  }
}

No navegador, chame o seu próprio backend em vez de colocar um token do Dailybot no código da página, e pause o loop no visibilitychange enquanto a aba estiver oculta.

Referência