Repository navigation
fix(aggregator): age out done after 7d, collapse Done recently, filter retired Done-table class (#1146) #2430
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3c45bb1
5cb2fd4
fe2a715
100ce70
3df9cf8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| /** | ||
| * Board composition policy for finished work (#1146). | ||
| * | ||
| * The WIP board is a view of work in flight, not a completion log: | ||
| * - Done items stay on the board for DONE_BOARD_WINDOW_MS (7 days) | ||
| * after their last update, then drop off entirely — GitHub is the | ||
| * record of finished work. | ||
| * - Kaneo cards in the retired "ROADMAP Done table" bookkeeping class | ||
| * (the class Drew killed on 2026-10-26) are filtered out at the | ||
| * aggregator; the board provenance states this filter. | ||
| * - The renderer collapses the surviving done items behind a | ||
| * "Done recently (N)" expander (see renderBoardBody). | ||
| * | ||
| * Applied in two places so no board source can bypass it: the Kaneo | ||
| * source filters while assembling the board (sources/live.ts), and | ||
| * renderBoardBody applies it again before rendering. | ||
| */ | ||
| import type { BoardItem } from "./model.ts"; | ||
|
|
||
| export const DONE_BOARD_WINDOW_MS = 7 * 24 * 60 * 60 * 1000; | ||
|
|
||
| /** Titles of the retired ROADMAP Done-table bookkeeping class. */ | ||
| export function isRetiredDoneTableTitle(title: string): boolean { | ||
| return /\broadmap\s+done\s+table\b/i.test(title); | ||
| } | ||
|
|
||
| /** | ||
| * A done item is recent when its last update is inside the window. | ||
| * An item with no parseable updatedAt is kept: its age is unproven, | ||
| * and dropping undated work silently would be worse than showing it. | ||
| */ | ||
| export function doneItemIsRecent(item: BoardItem, now: number): boolean { | ||
| if (!item.updatedAt) return true; | ||
| const t = Date.parse(item.updatedAt); | ||
| if (Number.isNaN(t)) return true; | ||
| return now - t <= DONE_BOARD_WINDOW_MS; | ||
| } | ||
|
|
||
| /** Drop aged-out done items and the retired Done-table class. */ | ||
| export function applyBoardDonePolicy(items: BoardItem[], now = Date.now()): BoardItem[] { | ||
| return items.filter((item) => { | ||
| if (item.source === "kaneo" && isRetiredDoneTableTitle(item.title)) return false; | ||
| if (item.status === "done" && !doneItemIsRecent(item, now)) return false; | ||
| return true; | ||
| }); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,7 @@ import { | |
| windowPercent, | ||
| } from "./derive.ts"; | ||
| import { latestInFamily, newClientToken, type DashAction } from "./actions.ts"; | ||
| import { applyBoardDonePolicy } from "./board-policy.ts"; | ||
| import { cardState, pendingVerb, type CardState } from "./card-state.ts"; | ||
| import { | ||
| DELEGATION_DEFAULT_OBJECT_REF, | ||
|
|
@@ -1373,6 +1374,11 @@ export function renderBoardBody( | |
| identities?: AuthorIdentity[]; | ||
| }, | ||
| ): string { | ||
| // #1146: finished work is not WIP — aged-out done items and the | ||
| // retired ROADMAP Done-table class never reach the board, whatever | ||
| // source assembled it (the Kaneo source filters too; see | ||
| // board-policy.ts). Surviving done renders collapsed, below. | ||
| board = applyBoardDonePolicy(board); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| const cols = new Map<string, BoardItem[]>(); | ||
| for (const item of board) { | ||
| const key = item.status === "done" ? "done" : item.status; | ||
|
|
@@ -1420,6 +1426,11 @@ export function renderBoardBody( | |
| .join(""); | ||
| // The in-flight lane is dated by the orchestration section that | ||
| // produced it (stamped on every synthetic entry as updatedAt). | ||
| if (k === "done") { | ||
| // Collapsed by default (#1146): a count + the last 7 days | ||
| // behind an expander, never a full column of finished cards. | ||
| return `<details class="donestrip"><summary><strong>Done recently (${colItems.length})</strong> — done in the last 7 days, collapsed by default; older done is aged off the board (GitHub is the record)</summary>${cards}</details>`; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
| const heading = | ||
| k === "in-flight" | ||
| ? `in flight — orchestrator · in flight · section ${fmtDate(colItems[0]?.updatedAt)} (${colItems.length})` | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ | |
| import { mkdir, readFile, rename, writeFile } from "node:fs/promises"; | ||
| import { dirname } from "node:path"; | ||
| import type { Config } from "../config.ts"; | ||
| import { applyBoardDonePolicy, isRetiredDoneTableTitle } from "../board-policy.ts"; | ||
| import { isAaSection } from "../aa.ts"; | ||
| import { isDnsSection } from "../dns.ts"; | ||
| import { | ||
|
|
@@ -84,6 +85,7 @@ interface KaneoTask { | |
| status: string; | ||
| priority?: string; | ||
| createdAt?: string; | ||
| updatedAt?: string; | ||
| externalLinks?: { | ||
| resourceType: string; | ||
| externalId: string; | ||
|
|
@@ -128,11 +130,18 @@ async function kaneoProjects( | |
| async function kaneoBoard(cfg: Config, cookie: string): Promise<BoardItem[]> { | ||
| const projects = await kaneoProjects(cfg, cookie); | ||
| const out: BoardItem[] = []; | ||
| // A task read that fails on every project is a failed fetch, not a | ||
| // successfully empty board (Codex review, PR #2430): board() must | ||
| // reach its catch path and serve fixtures with that provenance, | ||
| // while a successful read the done policy filters to empty stays | ||
| // live. Partial data (at least one page read) is still served. | ||
| let anyTaskPageRead = false; | ||
| for (const p of projects) { | ||
| const all: KaneoColumn[] = []; | ||
| for (let page = 1; ; page++) { | ||
| const r = await getJson(`${cfg.kaneo.baseUrl}/api/task/tasks/${p.id}?page=${page}`, { Cookie: cookie }); | ||
| if (!r.ok) break; | ||
| anyTaskPageRead = true; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| const body = r.json as { data?: { columns?: KaneoColumn[] }; pagination?: { totalPages: number } }; | ||
| const cols = body.data?.columns ?? []; | ||
| all.push(...cols); | ||
|
|
@@ -141,10 +150,15 @@ async function kaneoBoard(cfg: Config, cookie: string): Promise<BoardItem[]> { | |
| } | ||
| // The curated in-flight set, not every open issue: in-progress, | ||
| // in-review, and done (recent) columns only. To-do is the backlog. | ||
| // "Done (recent)" is enforced, not just commented (#1146): done | ||
| // cards older than 7 days drop off below via applyBoardDonePolicy, | ||
| // and the retired ROADMAP Done-table bookkeeping class is filtered | ||
| // here at the aggregator (the board provenance states the filter). | ||
| const wipStatuses = new Set(["in-progress", "in-review", "done"]); | ||
| for (const col of all) { | ||
| if (!wipStatuses.has(col.id)) continue; | ||
| for (const t of col.tasks) { | ||
| if (isRetiredDoneTableTitle(t.title)) continue; | ||
| const links: Link[] = (t.externalLinks ?? []) | ||
| .filter((l) => l.resourceType === "issue" || l.resourceType === "pull_request") | ||
| .map((l) => { | ||
|
|
@@ -160,14 +174,15 @@ async function kaneoBoard(cfg: Config, cookie: string): Promise<BoardItem[]> { | |
| source: "kaneo", | ||
| status: col.id, | ||
| priority: t.priority, | ||
| updatedAt: t.createdAt, | ||
| updatedAt: t.updatedAt ?? t.createdAt, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| kaneoUrl: kaneoTaskUrl(p.workspaceId, p.id, t.id), | ||
| links, | ||
| }); | ||
| } | ||
| } | ||
| } | ||
| return out; | ||
| if (projects.length > 0 && !anyTaskPageRead) throw new Error("kaneo task reads failed for every project"); | ||
| return applyBoardDonePolicy(out); | ||
|
dezren39 marked this conversation as resolved.
Resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
|
|
||
| // --- Grist -------------------------------------------------------------- | ||
|
|
@@ -665,33 +680,36 @@ export function liveSource(cfg: Config, opts?: { standingAuto?: () => Promise<bo | |
| try { | ||
| const cookie = await kaneoCookie(); | ||
| const board = await kaneoBoard(cfg, cookie); | ||
| if (board.length > 0) { | ||
| provenance.board = "live (Kaneo)"; | ||
| let merged = board; | ||
| if (cfg.projector.baseUrl) { | ||
| merged = await enrichWithRegistryLinks(cfg.projector.baseUrl, board, (b) => b.id); | ||
| provenance.board = "live (Kaneo + projector links)"; | ||
| } | ||
| // In-flight lane (see inFlightBoardItems above): synthetic | ||
| // entries from the fleet feed's orchestration section — the | ||
| // same feed read this source already uses for quota and | ||
| // orchestration — merged ahead of the Kaneo cards. The lane | ||
| // fails soft: no feed / no section = the Kaneo board alone. | ||
| try { | ||
| const feed = await getFeed(); | ||
| const orchSection = feed?.orchestration ? normalizeOrchestrationSection(feed.orchestration) : null; | ||
| if (orchSection) { | ||
| const inFlight = inFlightBoardItems(orchSection, merged); | ||
| if (inFlight.length) { | ||
| provenance.board += ` + orchestrator in-flight (${inFlight.length} lined-up, section ${orchSection.generatedAt})`; | ||
| merged = [...inFlight, ...merged]; | ||
| } | ||
| // A successfully fetched board is live data even when the | ||
| // done policy (#1146) filters it to empty: falling through to | ||
| // fixtures would replace the real (empty) board with | ||
| // illustrative work and hide the orchestrator in-flight lane | ||
| // merged below. Fixtures are only for a failed fetch (catch). | ||
| provenance.board = "live (Kaneo; done aged out after 7 days, retired ROADMAP Done-table cards filtered)"; | ||
|
dezren39 marked this conversation as resolved.
Resolved
|
||
| let merged = board; | ||
| if (cfg.projector.baseUrl) { | ||
| merged = await enrichWithRegistryLinks(cfg.projector.baseUrl, board, (b) => b.id); | ||
| provenance.board = "live (Kaneo + projector links; done aged out after 7 days, retired ROADMAP Done-table cards filtered)"; | ||
| } | ||
| // In-flight lane (see inFlightBoardItems above): synthetic | ||
| // entries from the fleet feed's orchestration section — the | ||
| // same feed read this source already uses for quota and | ||
| // orchestration — merged ahead of the Kaneo cards. The lane | ||
| // fails soft: no feed / no section = the Kaneo board alone. | ||
| try { | ||
| const feed = await getFeed(); | ||
| const orchSection = feed?.orchestration ? normalizeOrchestrationSection(feed.orchestration) : null; | ||
| if (orchSection) { | ||
| const inFlight = inFlightBoardItems(orchSection, merged); | ||
| if (inFlight.length) { | ||
| provenance.board += ` + orchestrator in-flight (${inFlight.length} lined-up, section ${orchSection.generatedAt})`; | ||
| merged = [...inFlight, ...merged]; | ||
| } | ||
| } catch { | ||
| /* the Kaneo board still renders without the lane */ | ||
| } | ||
| return merged; | ||
| } catch { | ||
| /* the Kaneo board still renders without the lane */ | ||
| } | ||
| return merged; | ||
| } catch (err) { | ||
| provenance.board = `fixtures (kaneo unreachable: ${err instanceof Error ? err.message : err})`; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Script executed:
rg -n -i 'roadmap.{0,20}done.{0,10}table|retired.*done.table' pm-stack/aggregatorRepository: developing-today/code
Length of output: 2356
Script executed:
Repository: developing-today/code
Length of output: 15357
Exclude the retired-title matcher from active Kaneo work.
The unanchored pattern in
isRetiredDoneTableTitledrops any Kaneo card whose title contains "ROADMAP Done table" anywhere. This includes in-progress and in-review cards. A card such as "Investigate ROADMAP Done table migration" never reaches the board, and the provenance text still says that only retired bookkeeping cards are filtered. Match the retired class by an anchored title pattern, or by a field that identifies bookkeeping cards, so that active work with the phrase in its title stays on the board.