Skip to content

Delivery board: project view on the Tasks board (issue/PR pipeline, five columns + metrics) #1173

Closed

Description

@Birfy

Written-by: Chief

Goal

Owner asked (2026-10-25): the delivery board we keep as a Markdown file should be viewable inside FlyBot, not as an attachment. #1058/#1099 shipped the per-Bot Tasks board (3 columns, Bot task records); this issue is a different surface: a project delivery board whose cards are the repository's GitHub issues and PRs, laid out by delivery stage.

Today

  • #1058 Tasks dialog: columns Not started / In progress / Done; cards are Bot task records (track_tasks). It has no GitHub data, no issue→PR linkage, no metrics.
  • The team currently builds the delivery board out of process (Markdown file, columns: 原始 issue / 待处理 issue / 开发中 issue / review 中 PR / 今日已合 PR + 已关 issue) and regenerates it every 30 minutes by calling the GitHub REST API from a shell script.

Expected behavior

  1. A second view in the existing board surface (same sidebar entry, view switcher: Tasks | Delivery) — not a separate app.
  2. Five columns (default order, labels localized):
    1. 原始 issue — every open issue not yet triaged, plus those waiting on a human decision (labelled needs-human / needs-info).
    2. 待处理 issue — triaged, agent-ready, not yet claimed.
    3. 正在处理 issue — claimed by an agent (agent-working); the linked PR, if any, is shown on the card.
    4. 正在 review 的 PR — open PRs with no verdict yet, or a verdict at the current tip that is not merged.
    5. 今日完成 — PRs merged today plus issues closed today (including PRs closed without merging).
  3. Card fields: all labels (state labels first), current owner, linked #n (issue ↔ PR), created/updated time, click-through link. Birfy/human accounts rendered as a person, not as a Bot.
  4. Header metrics for today (Asia/Shanghai 00:00 boundary) and a 7-day comparison: new issues / closed issues / new PRs / merged PRs, median issue-close time, median PR-merge time, CI runs failed vs total, and open counts. A "median age of open issues" line is wanted too (today's medians only count what closed, so they flatter the board).
  5. Read-only: the board never writes to GitHub (no labels, no comments, no enqueue). It is a view.
  6. Not dependent on a Bot being awake: synchronization runs host-side on a schedule (reuse the existing routines/timer path), the UI reads the last snapshot and shows its timestamp. Empty state when the repo has no GitHub connection.

Scope / constraints

  • Data source: GitHub REST through the host's existing GitHub channel/token; do not put a token in the client.
  • Rate limits matter: per-day CI aggregation is expensive (the current script spends 30–60 s per rebuild because it queries /actions/runs?created=from..to once per day). Cache snapshots and use conditional requests; the UI must stay responsive on a stale snapshot.
  • Owner attribution follows existing repo conventions: claim comment Claimed by <Name>, PR body Written-by: / Owner:, then assignee.
  • PR status badge must distinguish: INC (rework), PASS waiting for enqueue, PASS waiting for merge, CI red, CI running, no verdict; cancelled check runs are "superseded", not red.
  • Do not change the Tasks view semantics from Per-bot task list + Kanban board (conversation-derived tasks, tree + group admin views) #1058/Task board drives Bot planning: group overall tasks + per-Bot subtasks #1099 (parent/child, group overall task).

Out of scope

Acceptance

  • Sidebar entry opens the board with a Tasks | Delivery switch; Delivery shows the five columns from the same snapshot.
  • Cards show labels, owner, linked issue/PR number and link; an issue↔PR pair is visually connected.
  • Today metrics + 7-day comparison render from the snapshot, with the snapshot timestamp visible.
  • Sync runs on a host-side schedule with caching; UI renders from the last snapshot when offline/rate-limited, and says so.
  • Read-only invariant covered by a test (no write API called from the board module).
  • Changelog fragment under changelog.d/; docs updated where the board is described.
  • npm run verify green.

Related

Activity

  1. added
    enhancementNew feature or request
    agent-taskWritten with the agent-task form; not approval for agents (that is agent-ready)
    area:uiWeb/desktop UI and copy
    area:automationScheduled tasks and automation
    on Oct 25, 2026
  2. Birfy commented on Oct 25, 2026

    @Birfy
    CollaboratorAuthor

    Written-by: Chief

    Implementation touch-points verified on main (7fe7149a), so the agent that takes this does not have to rediscover them.

    Where it goes

    Concern File What changes
    Column model + card→column mapping core/src/shared/task-board.js (COLUMNS, PRIORITIES, COLUMN_OF, columnOf) Do not change these. They are the track_tasks semantics (#1058/#1099). Add a separate export for the delivery columns, e.g. DELIVERY_COLUMNS = ['raw', 'ready', 'working', 'review', 'done-today'].
    Board UI core/src/client/kanban.js (boardColumns(), KanbanDialog, BoardColumn) Add a view switch (Tasks | Delivery) next to the existing scope rail; Delivery renders the five columns from the delivery snapshot, keeps the Tasks path untouched.
    Column validation core/src/host/tasks.js (readColumn()) Untouched — delivery cards are not tasks and must not be written through the task API.
    Tool schema core/src/host/tools.js (track_tasks) Untouched.
    Snapshot host API new module under core/src/host/ (e.g. delivery-board.js) Fetch + cache the GitHub snapshot, expose one read method to the client.

    Mirror rule (bit us in #646): every core/ edit needs its patches/ds-bot/files/... twin, and tools.js/host API changes go through patches/ds-bot/0002-flybot-api-host.patch; node scripts/ds-bot-patches.mjs check must exit 0 before the tip is pushed.

    Metrics — exact formulas

    Reference implementation is the out-of-process generator the team runs today; these are its definitions, use them verbatim:

    • Day boundary: Asia/Shanghai 00:00. "Today" = [00:00, now); the 7-day comparison covers the seven whole days before today.
    • New issues / new PRs: created_at inside the window (issues excluding PRs; PRs counted separately).
    • Closed issues: closed_at in the window, split by state_reason (completed / not_planned / duplicate).
    • Merged PRs: merged_at in the window. Closed-unmerged PRs are the fifth column's third bucket.
    • Median issue-close time: closed_at - created_at over the window; median PR-merge time: merged_at - created_at. Medians, not means (a single 3-day-old PR must not skew the number).
    • CI: /actions/runs?created=<from>..<to> per day. Known limit: the runs list caps at 1000 items per query and busy days exceed it (today: 2500 runs, only the first 1000 counted) — the payload must carry the counted/total caveat, not silently under-report. conclusion: cancelled counts as "superseded", never as red.
    • Open counts: open PRs, open issues, and how many of those open issues carry needs-human/needs-info (that last number is the real bottleneck the owner watches).
    • Also wanted: median age of currently-open issues — the closed-based medians only describe what left the board and flatter the numbers.

    Card data

    • Labels: all of them, state labels first.
    • Owner: claim comment Claimed by <Name> → PR body Written-by: / Owner: → assignee. Birfy and hsliuustc0106 render as people, not Bots.
    • Linkage: an issue and its PR are matched on title and body (Fixes #n); show the pair as one connected card, PR with its short sha.
    • PR badge: INC / PASS · waiting for enqueue / PASS · waiting for merge / CI red / CI running / no verdict.

    Safety and cost

    • Read-only. The board must never call a write endpoint (no label, comment, close, merge, enqueue). Add a test asserting the delivery module issues no POST/PATCH/PUT/DELETE.
    • GitHub token stays host-side; the client only receives the snapshot JSON.
    • Sync is host-scheduled with a cached snapshot (a full rebuild costs 30–60 s today because CI is queried per day); ETag/conditional requests per endpoint; the UI shows the snapshot timestamp and, when rate-limited or offline, renders the previous snapshot with a visible "as of " marker.

    Suggested split (owner can cut the scope)

    • M1 — host snapshot + read-only five-column rendering from a cached JSON, no header metrics. Enough to actually use the board.
    • M2 — header metrics, 7-day comparison table, CI stats, median open-issue age.
    • M3 — refresh policy polish, empty/no-connection states, caching hardening.
  3. Birfy commented on Oct 25, 2026

    @Birfy
    CollaboratorAuthor

    Written-by: Chief

    Owner decision (2026-10-25): build M1 + M2 now, M3 later. Concretely the agent should deliver, in one PR:

    • M1 — host-side GitHub snapshot (cached) + a read-only 五列 delivery view reachable from the existing board entry.
    • M2 — header metrics for today (Asia/Shanghai day boundary) + 7-day comparison + CI failed/total + median age of currently-open issues, all from the same snapshot.

    M3 (refresh policy polish, empty/no-connection states, caching hardening) is explicitly out of this PR unless it falls out for free.

    Constraints unchanged: read-only (no write endpoint from the board), token host-side only, Tasks view semantics untouched, core/ edits mirrored into patches/ds-bot/files/... with node scripts/ds-bot-patches.mjs check exit 0.

  4. Birfy commented on Oct 25, 2026

    @Birfy
    CollaboratorAuthor

    Written-by: Chief

    Two host-side facts verified on main (7fe7149a), so the snapshot module does not need new plumbing:

    1. Token path already exists. core/src/host/connectors.js defines GITHUB_SECRET = 'GITHUB_TOKEN', and connectors.js already reads it with rt.secretValue(GITHUB_SECRET). The delivery snapshot module should read the token the same way (server-side only) instead of adding an env var or a second secret store.
    2. HTTP helper already exists. core/src/host/update.js / dsh-update.js call api.github.com via a local fetchJson(url, headers) with a user-agent of PKG.name/PKG.version — reuse that shape, but pass the token here, because unauthenticated REST is 60 req/h and this snapshot needs several dozen per rebuild.

    Consequence for the design: the snapshot must degrade cleanly when no GitHub token is saved (empty state with a "connect GitHub" hint, per the acceptance list), and it must not print or log the token — the host already redacts GITHUB_TOKEN values in harness.js / experience.js, so pass the raw token only into the request headers.

  5. Birfy commented on Oct 25, 2026

    @Birfy
    CollaboratorAuthor

    Written-by: Triage

    Triaged at main 7fe7149afd5f23778cfcb3956314252e0709bab2 (the sha your comment names). Executable; no duplicate.

    Boundary vs existing issues

    Scope: M1 + M2 in one PR (owner decision, comment 6397738973); M3 out

    Where it goes (verified on this sha):

    • View switch: core/src/client/kanban.js:317 KanbanDialog already owns the dialog; the rail is built at :318 (boardRail) and is per Bot/group. Delivery is repo-wide: when Delivery is selected the Bot/group rail must not be shown and must not be repointed at a Bot.
    • Tasks path stays byte-identical: :24 boardColumns, :272 BoardView, :295 the hard-coded ['not-started','in-progress','done'] map, :323 useBoardTasks.
    • Delivery data: actions.deliveryBoard() plus its own hook next to useBoardTasks:150; snapshot served by a new host module core/src/host/delivery-board.js.
    • Entry point unchanged: core/src/client/index.js:494 openBoard, board mounted at core/src/client/overlays.js:47 / :59.
    • Token stays host-side: core/src/host/connectors.js:22 GITHUB_SECRET, base :30; signIn at :41 already takes an injected fetch — that is the seam to test on; rt.secretValue at :337.

    Do not touch: core/src/shared/task-board.js:5 COLUMNS, :8 COLUMN_OF, :19 columnOf; core/src/host/tasks.js:113 readColumn and the write paths :423 / :636 / :649; core/src/host/tools.js:823 track_tasks. Delivery columns are a separate export (DELIVERY_COLUMNS).

    Sync — narrowed on purpose: M1 is host fetch + cached snapshot + conditional requests, refreshed on demand when the cache is stale; the UI always renders the last snapshot and shows as of <time>. No background timer or recurring task in this PR: that is M3 refresh policy, and a DSH schedule costs a turn (#1166). Empty / no-GitHub-connection: one honest line in M1, polish in M3.

    Metrics: formulas verbatim from comment 6397729026 (Asia/Shanghai 00:00, medians not means, cancelled = superseded). /actions/runs is queried per day and caps at 1000: the payload must carry counted/total and the UI must show that caveat — never silently under-report.

    Acceptance

    1. Delivery tab renders the five columns from one snapshot; Tasks output unchanged (core/test/kanban.test.js green with no edits to boardColumns / BoardView).
    2. Card: all labels (state first), owner via claim comment → PR body → assignee, linked #n issue↔PR shown as one connected card, Birfy/hsliuustc0106 as people. PR badge distinguishes INC / PASS-waiting-enqueue / PASS-waiting-merge / CI red / CI running / no verdict.
    3. Header: today + 7-day comparison + CI failed/total (with the counted caveat) + median age of currently-open issues, all from the same snapshot, snapshot timestamp visible.
    4. Read-only, tested: the delivery module issues no POST/PATCH/PUT/DELETE; assert on the injected fetch and fail on any write path (labels, comments, close, merge, queue). A module that can write must fail this test.
    5. Token host-side only: the serialized snapshot the client receives carries no token/Authorization field.
    6. Mirror: new host file mirrored at patches/ds-bot/files/src/host/delivery-board.js; edits to upstream files (core/src/client/index.js, overlays.js) regenerated via node scripts/ds-bot-patches.mjs generate (scripts/ds-bot-patches.mjs:381); check must exit 0 (:380, comparison :247-271); the new test mirrored under patches/ds-bot/files/test/.
    7. Changelog fragment changelog.d/1173-delivery-board.added.md, docs updated where the Tasks board is described, npm run verify green.

    Owner: Dev
    Reviewer: named in the PR body (must not be Dev)

  6. added
    agent-readyMaintainer-approved spec: an agent may take it. Only maintainers set this; never in a template
    agent-workingAn agent has claimed this issue (the claim comment names the branch)
    on Oct 25, 2026
  7. 3 remaining items

  8. removed
    agent-readyMaintainer-approved spec: an agent may take it. Only maintainers set this; never in a template
    on Oct 25, 2026
  9. removed
    agent-workingAn agent has claimed this issue (the claim comment names the branch)
    on Oct 25, 2026
  10. removed
    agent-taskWritten with the agent-task form; not approval for agents (that is agent-ready)
    on Oct 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:automationScheduled tasks and automationarea:uiWeb/desktop UI and copyenhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions