From c2f8f2945272bd19d62b00065598110b4a9c040b Mon Sep 17 00:00:00 2001 From: Warren <28354219+jayminwest@users.noreply.github.com> Date: Tue, 16 Jun 2026 03:12:55 +0000 Subject: [PATCH 1/4] Bump version to 0.9.1 (warren-fade) Set package.json version and src/index.ts VERSION to 0.9.1 in sync, add a matching CHANGELOG.md 0.9.1 section summarizing the workspace chat ordering/dedup fix (warren-f4b8), bounded transcript scrolling (warren-5755), and leaked .pi/sessions transcript removal (warren-4c8d). Regenerate docs/openapi.yaml (embeds package.json version). Closes warren-fade --- CHANGELOG.md | 25 +++++++++++++++++++++++++ docs/openapi.yaml | 2 +- package.json | 2 +- src/index.ts | 2 +- 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b16d9de..64d6f3dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.9.1] — 2026-06-16 + +Low-risk cleanup batch for the **Workspace** surface (plan pl-2e59): chat +ordering/dedup correctness, bounded transcript scrolling, and removal of a +leaked agent transcript. + +### Fixed + +- **`fix(ui)`** — workspace chat ordering + dedup (warren-f4b8). Transcript + and stream bubbles now merge on a single unified chronological ordering key + across the transcript/stream boundary instead of concatenating two + seq-sorted groups, and `buildChatMessages` dedupe collapses a streamed + assistant turn and its persisted transcript copy into one bubble + (`src/ui/src/components/chat-messages.ts`). +- **`fix(ui)`** — unbounded `/workspaces` page growth (warren-5755). The + conversation card's ancestor height is bounded so `Chat.tsx`'s internal + `flex-1 min-h-0 overflow-y-auto` engages and the transcript scrolls + internally instead of growing the page + (`src/ui/src/pages/conversation-detail/conversation-surface.tsx`). + +### Removed + +- **`chore`** — a stray `.pi/sessions` agent transcript committed by PR #340 + that was not named in its title (warren-4c8d). + ## [0.9.0] — 2026-06-14 Leveret and Plots collapse into a single tabbed **Workspace** surface, and diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 8b663210..ffee5c45 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -3,7 +3,7 @@ openapi: 3.1.0 info: title: warren HTTP API - version: 0.9.0 + version: 0.9.1 description: >- Auto-generated from `src/server/handlers/index.ts`'s `ROUTE_TABLE`. Run `bun run gen:openapi` to refresh; CI fails if this schema drifts from the handler module. Request/response bodies are diff --git a/package.json b/package.json index 828c0d89..06b03352 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@os-eco/warren-cli", - "version": "0.9.0", + "version": "0.9.1", "description": "Self-hostable control plane for ephemeral cloud agents — spawn sandboxed agents at your GitHub repos, watch them work live, steer them, get a branch back", "type": "module", "bin": { diff --git a/src/index.ts b/src/index.ts index 4f117dcd..ced4ca84 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,4 +4,4 @@ * `Client` class is deferred to V2 (SPEC §8.3). */ -export const VERSION = "0.9.0"; +export const VERSION = "0.9.1"; From 2aa95e6b5df81c5a70b3577e157a88f45358937c Mon Sep 17 00:00:00 2001 From: Warren <28354219+jayminwest@users.noreply.github.com> Date: Tue, 16 Jun 2026 03:14:12 +0000 Subject: [PATCH 2/4] chore: seeds + mulch state for warren-fade (0.9.1 bump) --- .mulch/expertise/release.jsonl | 1 + .seeds/issues.jsonl | 4 ++-- .seeds/plans.jsonl | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.mulch/expertise/release.jsonl b/.mulch/expertise/release.jsonl index d630a078..e10c9d63 100644 --- a/.mulch/expertise/release.jsonl +++ b/.mulch/expertise/release.jsonl @@ -3,3 +3,4 @@ {"type":"failure","classification":"tactical","recorded_at":"2026-05-27T16:37:14.284Z","evidence":{"commit":"9e62847223bacb9a2ab33c87dab91379d6fc5ed9"},"description":"Releasing warren regenerates docs/openapi.yaml because gen:openapi embeds package.json version into info.version. After bumping package.json + src/index.ts VERSION, run 'bun run gen:openapi' (and 'bun run gen:docs' for safety) before check:all — otherwise gen:openapi:check fails inside check:all and the release commit is blocked.","resolution":"After version bump in package.json + src/index.ts, run 'bun run gen:openapi && bun run gen:docs' before check:all so the generated docs match the new version.","id":"mx-301178"} {"type":"failure","classification":"tactical","recorded_at":"2026-05-29T19:30:17.357Z","evidence":{"seeds":"warren-52fb","commit":"09c2d99cff927fc63b3205ad2ca14ef239c2687a"},"description":"check:coverage fails locally in a fresh burrow with 'Cannot find package zod/drizzle-orm/js-yaml/pino/pg' errors: 164 tests fail at module load, depressing Bun's function-coverage aggregate (~85.7%) below the 87.09 floor. The bun-version mismatch (.bun-version pins 1.3.14; burrow had 1.2.23) is a red herring — coverage barely moves across versions.","resolution":"Run 'bun install' first — deps were not installed in the sandbox. With deps present all 2433 tests pass and coverage is 88.45% funcs / 91.46% lines, clearing the floor. Always 'bun install' before trusting a red coverage/test gate in a fresh burrow.","id":"mx-6217bd"} {"type":"convention","classification":"tactical","recorded_at":"2026-06-14T03:36:27.886Z","evidence":{"commit":"3bc9e1c43104813ed1c926dd3628d6a460152a9a"},"content":"Minor (not patch) bumps are reserved for user-facing surface changes (e.g. the pl-0008 Workspace collapse: 0.8.10 -> 0.9.0); the release.md flow defaults to patch otherwise. After bumping package.json + src/index.ts, always 'bun run gen:openapi' since the OpenAPI doc embeds the package version and gen:openapi:check will fail CI otherwise.","id":"mx-cdcdc7"} +{"type":"convention","classification":"tactical","recorded_at":"2026-06-16T03:14:06.448Z","evidence":{"commit":"c2f8f2945272bd19d62b00065598110b4a9c040b"},"content":"Version-bump seeds may land before their nominal 'land last after X PRs merge' gate if X is unmerged: write the CHANGELOG to summarize only what actually merged on main, not the seed's aspirational scope (e.g. 0.9.1 shipped the workspace chat + transcript fixes but the pl-f700 logging seeds were still open).","id":"mx-95fc49"} diff --git a/.seeds/issues.jsonl b/.seeds/issues.jsonl index 64da79c4..3e80b970 100644 --- a/.seeds/issues.jsonl +++ b/.seeds/issues.jsonl @@ -722,10 +722,10 @@ {"id":"warren-6060","title":"canopy: typed frontmatter via --fm key:=value JSON syntax (cn --fm stringifies all values, breaking boolean flags like auto_plan_run — warren-5f07 worked around it with defensive coercion)","status":"open","type":"feature","priority":2,"createdAt":"2026-06-14T00:01:28.201Z","updatedAt":"2026-06-14T00:01:28.201Z"} {"id":"warren-8363","title":"Release — minor version bump via .claude/commands/release.md flow","status":"closed","type":"task","priority":2,"createdAt":"2026-06-14T01:37:50.516Z","updatedAt":"2026-06-14T03:37:41.815Z","description":"\nAdopted into plan pl-0008.\n\nParent seed: warren-dde5 — Collapse Leveret + Plot into a single tabbed Workspace surface\nPlan template: feature\nPlan approach: Make the Plot the spine and the conversation a facet of it. Introduce one 'Workspace' top-level section: a single cross-project list (one row per Plot, with an active-conversation indicator) and one tabbed detail page (/workspace/:id) that…\n\nRun `sd plan show pl-0008` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n\n\nAfter the Workspace collapse lands (steps 1-13, ending warren-3f94 with check:all green), cut a release by running the .claude/commands/release.md flow with an explicit MINOR bump: review git log since the last tag, bump the version in BOTH package.json (\"version\") and src/index.ts (export const VERSION) to the next minor (X.Y+1.0 -> e.g. patch/minor reset), move CHANGELOG.md [Unreleased] items into the new dated version section, update CLAUDE.md/README.md if command counts or structure changed, then commit. The release workflow (.github/workflows/release.yml) fails if package.json and src/index.ts disagree, so keep them in sync. This is a minor (not patch) bump because the Workspace collapse is a user-facing surface change (Leveret + Plots merged into one tabbed Workspace, brainstorm/formalize path removed).","plan_id":"pl-0008","blocks":["warren-dde5"],"closedAt":"2026-06-14T03:37:41.815Z"} {"id":"warren-8436","title":"R-20: colony / multi-repo tier layered on top of the Workspace surface","status":"open","type":"feature","priority":3,"createdAt":"2026-06-14T03:27:15.092Z","updatedAt":"2026-06-14T03:27:30.187Z","labels":["roadmap"],"description":"Layer a multi-repo / colony shaping tier on top of the single Workspace surface landed by pl-0008 (warren-dde5). The Workspace list (one row per Plot, cross-project spine) and the /workspace/:id tabbed detail (Shape -> Plan -> Run -> Activity) are the designated home for R-20: extend the list to span multiple repos/colonies, let a single Plot fan shaping + plan-runs across repos, and surface colony-level status. Builds directly on src/ui/src/pages/Workspace.tsx + WorkspaceDetail.tsx and the SPEC 11.O.Plot.UI Workspace note."} -{"id":"warren-faab","title":"Cleanup batch → 0.9.1: logging hardening (pl-f700) + workspace chat fixes + leaked transcript removal + version bump","status":"open","type":"epic","priority":1,"createdAt":"2026-06-14T05:53:53.355Z","updatedAt":"2026-06-16T03:03:49.017Z","description":"Umbrella seed for Plot plot-b11c5999 (\"Workspaces Testing\"). Low-risk cleanup batch capped by a 0.9.1 patch bump. Logging/traceability hardening is the already-approved plan pl-f700 (6 seeds: warren-c686 → warren-fc6e → warren-af76 → warren-26c2 → warren-9f06 → warren-b2dd) — adopted as a contiguous sub-sequence, NOT re-authored. This seed's plan covers the net-new tail: workspace chat ordering/dedup fix, workspace chat unbounded-growth fix, leaked .pi/sessions transcript removal (warren-4c8d), and the 0.9.1 version bump. Each seed lands as its own PR, walked serially, gated on the prior PR merging; version bump lands last after every logging + chat + transcript PR merges. All work passes 'bun run check:all'.","plan_id":"pl-2e59","blockedBy":["warren-4c8d","warren-fade"]} +{"id":"warren-faab","title":"Cleanup batch → 0.9.1: logging hardening (pl-f700) + workspace chat fixes + leaked transcript removal + version bump","status":"open","type":"epic","priority":1,"createdAt":"2026-06-14T05:53:53.355Z","updatedAt":"2026-06-16T03:14:06.231Z","description":"Umbrella seed for Plot plot-b11c5999 (\"Workspaces Testing\"). Low-risk cleanup batch capped by a 0.9.1 patch bump. Logging/traceability hardening is the already-approved plan pl-f700 (6 seeds: warren-c686 → warren-fc6e → warren-af76 → warren-26c2 → warren-9f06 → warren-b2dd) — adopted as a contiguous sub-sequence, NOT re-authored. This seed's plan covers the net-new tail: workspace chat ordering/dedup fix, workspace chat unbounded-growth fix, leaked .pi/sessions transcript removal (warren-4c8d), and the 0.9.1 version bump. Each seed lands as its own PR, walked serially, gated on the prior PR merging; version bump lands last after every logging + chat + transcript PR merges. All work passes 'bun run check:all'.","plan_id":"pl-2e59","blockedBy":["warren-4c8d"]} {"id":"warren-f4b8","title":"Fix workspace chat ordering + dedup in src/ui/src/components/chat-messages.ts: merge transcript + stream bubbles on a single unified ordering key (chronological across the transcript/stream boundary) instead of concatenating the two seq-sorted groups, and tighten buildChatMessages dedupe so a streamed assistant turn and its persisted transcript copy collapse to one bubble; add regression tests to chat-messages.test.ts","status":"closed","type":"bug","priority":1,"plan_step_index":0,"description":"\nStep 1 of plan pl-2e59.\n\nParent seed: warren-faab — Cleanup batch → 0.9.1: logging hardening (pl-f700) + workspace chat fixes + leaked transcript removal + version bump\nPlan template: feature\nPlan approach: Walk the net-new tail serially — one seed per PR, each gated on the prior PR merging — mirroring the polish-pass convention and the Plot's stated constraint. Order: chat ordering/dedup fix → chat unbounded-growth fix → leaked-transcript…\n\nRun `sd plan show pl-2e59` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n","createdAt":"2026-06-14T05:54:42.639Z","updatedAt":"2026-06-16T02:52:31.503Z","plan_id":"pl-2e59","blocks":["warren-5755","warren-faab"],"closedAt":"2026-06-16T02:52:31.503Z"} {"id":"warren-5755","title":"Fix unbounded /workspaces growth: bound the conversation card's ancestor height in src/ui/src/pages/conversation-detail/conversation-surface.tsx (min-h-[60vh] → a bounded height) so Chat.tsx's existing internal flex-1 min-h-0 overflow-y-auto engages and the transcript scrolls internally instead of growing the page; no new scroll container","status":"closed","type":"bug","priority":2,"plan_step_index":1,"description":"\nStep 2 of plan pl-2e59.\n\nParent seed: warren-faab — Cleanup batch → 0.9.1: logging hardening (pl-f700) + workspace chat fixes + leaked transcript removal + version bump\nPlan template: feature\nPlan approach: Walk the net-new tail serially — one seed per PR, each gated on the prior PR merging — mirroring the polish-pass convention and the Plot's stated constraint. Order: chat ordering/dedup fix → chat unbounded-growth fix → leaked-transcript…\n\nRun `sd plan show pl-2e59` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n","createdAt":"2026-06-14T05:54:42.639Z","updatedAt":"2026-06-16T03:03:49.017Z","plan_id":"pl-2e59","blocks":["warren-4c8d","warren-faab"],"extensions":{"role":"pi","lastRunId":"run_90fqekdeztrt","lastRunAt":"2026-06-16T02:57:44.047Z"},"closedAt":"2026-06-16T03:03:49.017Z"} -{"id":"warren-fade","title":"Bump version to 0.9.1: set package.json \"version\" and src/index.ts VERSION to 0.9.1 in sync (release workflow fails on drift) and add a matching CHANGELOG.md 0.9.1 section summarizing the logging hardening (pl-f700), workspace chat fixes, and leaked-transcript removal; land last, after every pl-f700 logging PR plus the chat + transcript PRs merge","status":"open","type":"task","priority":1,"plan_step_index":3,"description":"\nStep 4 of plan pl-2e59.\n\nParent seed: warren-faab — Cleanup batch → 0.9.1: logging hardening (pl-f700) + workspace chat fixes + leaked transcript removal + version bump\nPlan template: feature\nPlan approach: Walk the net-new tail serially — one seed per PR, each gated on the prior PR merging — mirroring the polish-pass convention and the Plot's stated constraint. Order: chat ordering/dedup fix → chat unbounded-growth fix → leaked-transcript…\n\nRun `sd plan show pl-2e59` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n","createdAt":"2026-06-14T05:54:42.639Z","updatedAt":"2026-06-14T05:54:42.639Z","plan_id":"pl-2e59","blockedBy":["warren-4c8d"],"blocks":["warren-faab"]} +{"id":"warren-fade","title":"Bump version to 0.9.1: set package.json \"version\" and src/index.ts VERSION to 0.9.1 in sync (release workflow fails on drift) and add a matching CHANGELOG.md 0.9.1 section summarizing the logging hardening (pl-f700), workspace chat fixes, and leaked-transcript removal; land last, after every pl-f700 logging PR plus the chat + transcript PRs merge","status":"closed","type":"task","priority":1,"plan_step_index":3,"description":"\nStep 4 of plan pl-2e59.\n\nParent seed: warren-faab — Cleanup batch → 0.9.1: logging hardening (pl-f700) + workspace chat fixes + leaked transcript removal + version bump\nPlan template: feature\nPlan approach: Walk the net-new tail serially — one seed per PR, each gated on the prior PR merging — mirroring the polish-pass convention and the Plot's stated constraint. Order: chat ordering/dedup fix → chat unbounded-growth fix → leaked-transcript…\n\nRun `sd plan show pl-2e59` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n","createdAt":"2026-06-14T05:54:42.639Z","updatedAt":"2026-06-16T03:14:06.231Z","plan_id":"pl-2e59","blockedBy":["warren-4c8d"],"blocks":["warren-faab"],"closedAt":"2026-06-16T03:14:06.231Z"} {"id":"warren-d426","title":"gatewatch: 4 Article IX-protected merges in window lack human sign-off record","status":"open","type":"task","priority":1,"createdAt":"2026-06-14T10:15:36.224Z","updatedAt":"2026-06-14T10:15:36.224Z","description":"Constitution Article IX requires that any merged change to docs/CONSTITUTION.md, the gatewatch/ratchetwatch/tastewatch entries in .canopy/, or audit entries in .warren/triggers.yaml carry EXPLICIT human review, recorded in the seed tracker (the warren-ea20 ratification-record pattern: 'explicitly human-reviewed and human-merged by the operator... This seed is the sign-off evidence gatewatch's Article IX check looks for'). Four PRs merged in the last 36h touch protected paths and NONE has a corresponding ratification/sign-off seed. The auto-merge.yml Article IX gate (intact, untouched in window) would have BLOCKED auto-merge on these, implying a human merge occurred — but with no recorded ratification, gatewatch cannot verify the gate was honest. Per gatewatch mandate item 6, file priority 1.\n\nEVIDENCE (each modifies its own mandate / the constitution):\n- SHA 7d3cf95e (PR #364): docs/CONSTITUTION.md — adds the 'Audit Warden boundary' section (ingestion rule, digest cadence, meta-Plot, autonomy-promotion constraints). Closes warren-05ef (a WORK seed, not a sign-off record).\n- SHA e2d88409 (PR #362): .warren/triggers.yaml — adds the 'warden-digest' cron audit entry (0 5 * * 0). The constitution explicitly names warden-digest as Article IX-protected. Closes warren-4240 (work seed).\n- SHA f8ead2ff (PR #361): .canopy/prompts.jsonl — bumps gatewatch (v2), ratchetwatch (v2), tastewatch (v3) auditor system prompts to add warden-delivery instructions; ALSO edits the three audit prompt entries in .warren/triggers.yaml. This is the audit population editing its own running mandate (including this very gatewatch prompt). Work seed warren-7f62, not a ratification record.\n- SHA 5f41fef5 (PR #338): .canopy/prompts.jsonl — repins the tastewatch agent model claude-fable-5 -> claude-opus-4-8 (tastewatch entry v1 -> v2). Modifies a protected auditor .canopy entry. No ratification record. (Same PR also adds a biome.json formatter-exempt override carrying tracker pl-cf2a — Article II satisfied there; the Article IX gap is the .canopy auditor change.)\n\nWHAT ARTICLE IX REQUIRES: a recorded human sign-off (warren-ea20-style) per protected-path merge, OR revert. The gate forcing human-merge is necessary but not sufficient — the constitution treats the recorded ratification seed as the audit evidence.\n\nREMEDIATION IS NON-MECHANICAL (no plan attached): the fix is a human decision — file a ratification record for each PR (#364, #362, #361, #338) attesting the operator reviewed and merged it, or revert. A mechanical plan-run cannot self-grant this, and any plan touching these protected paths would itself re-trigger Article IX. Routed to the warden for human triage.\n\nDedupe: not covered by warren-d5e5 (PR #270), warren-4c8d (PR #340), or warren-ea20 (PR #337 only). warden delivery: undeliverable this patrol (WARREN_API_TOKEN unset -> /conversations 401); seed is the durable record.","labels":["audit","gatewatch"]} {"id":"warren-60ee","title":"ratchetwatch tightening: 2026-06-14","status":"open","type":"task","priority":3,"createdAt":"2026-06-14T10:45:24.898Z","updatedAt":"2026-06-14T10:46:18.235Z","description":"Mechanical ratchet tightening for the 2026-06-14 patrol (Constitution Article II — ratchets only tighten).\n\nMeasurements (clean env; node_modules repaired via bun install --frozen-lockfile):\n- Coverage: functions 88.90% vs floor 88.60% (slack 0.30pt); lines 91.65% vs floor 91.54% (slack 0.11pt). Both < 0.75pt → no floor raise this patrol.\n- File-size: no grandfathered entry has dropped below the 500-line global limit (smallest = src/plots/aggregate.ts at 506), so no satisfied entries to remove.\n- Bundle: 7-day net gzip-js -221 B (re-baseline-down in #384); raises sum ~1.9 KB << 20 KB → no finding.\n- Debt allowlist: empty → no finding.\n\nThe single mechanical tightening this patrol is the one-file-per-patrol grandfather decomposition: src/runs/pr.ts (659 lines, furthest over the 500 limit, not covered by any open seed). See the attached refactor plan. Per Article III no release step is included — this hygiene batches into the next real release.","labels":["audit","ratchetwatch"],"plan_id":"pl-88bb","blockedBy":["warren-db9a","warren-70d7"]} {"id":"warren-889a","title":"Grandfather-at-birth: src/runs/stream/bridge.test.ts pushed to 521 lines and exempted in be18ba73 (within 24h)","status":"open","type":"task","priority":3,"createdAt":"2026-06-14T10:45:36.305Z","updatedAt":"2026-06-14T10:45:36.305Z","description":"Constitution Article II finding (ratchets only tighten / nothing grandfathered at birth).\n\nEvidence (Article VIII):\n- File: src/runs/stream/bridge.test.ts — current 521 lines (limit 500).\n- Budget entry \"src/runs/stream/bridge.test.ts\": 521 ADDED in commit be18ba73e20fd4f3beee5cdb7e0563ef6b90cb6a (Jaymin West, 2026-06-13 11:51:08 -0700 = 18:51 UTC; tracker warren-df71, 'feat(runs): keep conversation runs alive...'), i.e. within the trailing 24h of this 2026-06-14 10:42 UTC patrol.\n- The file pre-existed (created 2026-05-27 in 22d16f5d) and was previously UNDER budget; warren-df71 grew it past 500 and added a fresh grandfather exception in the same diff rather than keeping the test under the limit (Article II: 'a new file written over the size limit is decomposed before merge, not exempted at write time' — same spirit applies to pushing a compliant file over the line).\n\nSeverity is low: only 21 lines over. Remedy is a small split/trim of bridge.test.ts back under 500 and removal of its budget entry — NOT planned by ratchetwatch this patrol because the one-decomposition-per-patrol slot went to the furthest-over file (src/runs/pr.ts, 659). Filed for human/gatewatch attention.\n\nDedupe: no existing seed matches 'bridge.test' or 'df71' (searched). Coordinate with gatewatch on be18ba73/warren-df71 rather than double-filing.","labels":["audit","ratchetwatch"]} diff --git a/.seeds/plans.jsonl b/.seeds/plans.jsonl index 584f2249..083d6302 100644 --- a/.seeds/plans.jsonl +++ b/.seeds/plans.jsonl @@ -65,6 +65,6 @@ {"id":"pl-da54","seed":"warren-d0ed","template":"feature","status":"done","revision":1,"sections":{"context":"Phase 2 of audit population (decision mx-1e041c). The three constitution auditors already run as cron triggers in .warren/triggers.yaml (gatewatch + ratchetwatch mechanical, tastewatch report-only) and file findings as seeds, but nothing synthesizes across them: there is no standing place where findings accumulate, get triaged, and turn into plans. warren-d0ed closes that gap with a Leveret warden — a single long-lived `mode:\"conversation\"` run bound to a dedicated meta-Plot that ingests findings over the EXISTING POST /conversations/:id/messages steering channel (already 202 today, src/server/handlers/conversations.ts postConversationMessageHandler), synthesizes a weekly digest, and proposes plans through the proven send-off->planner chain (sendOffConversationHandler -> conversation-merge-poller -> conversation-merge-dispatch). This is a deliberately THIN first slice: one standing conversation + weekly digest + plan proposals. Auditor-autonomy-promotion recommendations (from tastewatch's precision table) are explicitly deferred to a tracked follow-up. The whole slice is GATED on the Leveret repair arc (warren-87fe / pl-afed + the bridge warren-ce65 in pl-d2d9): live agent-authored intent (warren-ce65 bridge + warren-df1b plot ACL widening) and a default-on send-off->planner loop (warren-157a merge-poller default) must be working before warden findings can produce real plans. Because those repair-arc seeds are owned by other plans, this plan does not re-create or mutate them; it encodes the gate as a precondition in step 1 and in the acceptance criteria.","approach":"Reuse every existing primitive; add no new endpoint or dispatch path. (1) Bootstrap ONE standing meta-Plot and ONE standing Leveret conversation bound to it, created idempotently and resolvable by a stable, well-known identifier (a warren-config knob / well-known title) so other surfaces can find 'the warden conversation' deterministically. (2) Teach the three existing auditor cron procedures to deliver each finding to that conversation over POST /conversations/:id/messages (the 202 steering channel) — a prompt/agent-definition change, not a new code path. (3) Add a warden-digest cron trigger (reusing the existing kind:cron mechanism) that posts a 'synthesize the weekly digest' message to the standing conversation, so Leveret triages the week's accumulated findings and proposes at least one plan via the existing send-off->planner chain. (4) Prove the full loop with a deterministic acceptance scenario. (5) Close out the thin slice: file the deferred autonomy-promotion follow-up, document the warden boundary, and verify warren-d0ed's thin-slice acceptance. Chosen over a bespoke warden endpoint/daemon because the intent's constraints pin reuse of POST /conversations/:id/messages and forbid any new ingestion or dispatch primitive; the standing-conversation model also gives the digest a persistent transcript to triage instead of a per-finding fan-out.","alternatives":[{"name":"Per-finding conversation (one Leveret conversation per auditor finding)","rejected_because":"The intent explicitly requires a single STANDING conversation bound to a long-lived meta-Plot; per-finding conversations lose the cross-finding synthesis that makes a weekly digest possible and multiply lifetime/cost overhead."},{"name":"New warden ingestion endpoint or dispatch primitive that auditors call","rejected_because":"Constraint + non-goal forbid any new endpoint/dispatch/ingestion path — the warden must ride the existing POST /conversations/:id/messages 202 steering channel."},{"name":"Ship the warden now, ahead of the repair arc, and stub live intent","rejected_because":"The warden's value is proposing real plans through send-off->planner, which needs agent-authored live intent (warren-ce65 bridge + warren-df1b ACL) and a default-on merge poller (warren-157a). Stubbing those would ship a warden that cannot actually produce a plan."},{"name":"Include auditor-autonomy-promotion recommendations in this slice","rejected_because":"Non-goal: deferred to a tracked follow-up to keep the first slice thin (single standing conversation + weekly digest + plan proposals)."}],"steps":[{"title":"Bootstrap the standing warden: one long-lived meta-Plot + one standing mode:\"conversation\" Leveret run bound to it, created idempotently and resolvable by a stable well-known identifier (warren-config knob / well-known title) so auditors and the digest cron can find 'the warden conversation' deterministically. GATE: do not start until the Leveret repair arc has landed (live agent intent via warren-ce65 bridge + warren-df1b plot ACL, and default-on send-off->planner via warren-157a); verify the bridge + ACL + merge-poller default are in place as a precondition. Reuse resolveConversationPlot/createConversationHandler (src/server/handlers/conversations.ts) — no new creation primitive.","type":"feature","blocks":[2,3,4],"labels":["leveret","audit","warden"]},{"title":"Deliver auditor findings into the standing warden conversation over the EXISTING POST /conversations/:id/messages channel: update the gatewatch / ratchetwatch / tastewatch cron procedures (.warren/triggers.yaml prompts + their .canopy agent entries) so each finding is posted to the warden conversation (resolved via step 1's well-known id) as a 202 steering message. No new endpoint, ingestion, or dispatch primitive.","type":"feature","blocks":[4],"labels":["leveret","audit","warden"]},{"title":"Weekly digest trigger: add a warden-digest entry to .warren/triggers.yaml (reusing kind:cron) that posts a 'synthesize this week's audit digest and propose plans' message to the standing warden conversation via POST /conversations/:id/messages, so Leveret triages the accumulated findings transcript and drives the send-off->planner chain. Reuse re-wake (POST /conversations/:id/re-wake) if the anchoring run has idled. No new dispatch primitive.","type":"feature","blocks":[4],"labels":["leveret","audit","warden"]},{"title":"Acceptance scenario: a deterministic, idempotent scripts/acceptance/scenarios/ scenario (mirroring 33-leveret-conversation-loop.ts) that seeds the standing warden conversation, posts real-shaped auditor findings over POST /conversations/:id/messages, fires the digest message, and asserts Leveret synthesizes a digest AND proposes at least one plan through the existing send-off->planner chain (no new dispatch path). Self-cleans after itself.","type":"feature","blocks":[5],"labels":["leveret","audit","warden"]},{"title":"Close out the thin slice: file a tracked follow-up seed for auditor-autonomy-promotion recommendations from tastewatch's precision table (deferred per non-goal), document the warden boundary in LEVERET.md / docs/CONSTITUTION.md (standing conversation + meta-Plot + digest cadence, ingestion only via POST /conversations/:id/messages), run bun run check:all, and verify warren-d0ed's thin-slice acceptance is met so it closes.","type":"feature","blocks":[],"labels":["leveret","audit","warden"]}],"risks":["Repair-arc dependency not yet landed: warren-ce65 (bridge), warren-df1b (plot ACL), and warren-157a (merge-poller default) are still open. If the warden plan is dispatched before they merge, send-off cannot produce agent-authored intent or auto-dispatch a planner. Step 1's gate and acceptance criterion 1 exist to catch this; the plan is sequenced after the arc.","Standing-conversation lifetime: A long-lived mode:\"conversation\" anchoring run can idle-finalize (conversation.idleTimeoutMs, warren-005d); the digest cron and auditor posts must re-wake (POST /conversations/:id/re-wake) rather than assume a hot run, or findings posted while idle could be lost before replay.","Auditor procedure drift / Article IX review: Editing auditor prompts and .warren/triggers.yaml touches constitution-protected files (docs/CONSTITUTION.md Article IX) — changes need human-merge review and must not silently alter auditor autonomy.","Well-known id resolution fragility: Resolving 'the warden conversation' by title/config could break if duplicated; the bootstrap must be idempotent and assert a single canonical standing conversation per project."],"acceptance":["A single standing Leveret conversation bound to a dedicated long-lived meta-Plot exists, is created idempotently, and is resolvable by a stable well-known identifier.","All three auditors (gatewatch/ratchetwatch/tastewatch) deliver findings to the standing conversation via POST /conversations/:id/messages (202) with no new endpoint, ingestion, or dispatch primitive added.","The warden synthesizes a weekly digest from accumulated findings and proposes at least one plan through the existing send-off->planner chain, proven by a deterministic acceptance scenario.","An auditor-autonomy-promotion follow-up seed is filed and referenced; the warden boundary is documented; bun run check:all passes; warren-d0ed closes as the thin slice."]},"children":["warren-4372","warren-7f62","warren-4240","warren-6022","warren-05ef"],"createdAt":"2026-06-13T20:54:29.453Z","updatedAt":"2026-06-13T23:55:15.358Z","name":"Leveret warden — standing audit-triage conversation (thin slice)"} {"id":"pl-a141","seed":"warren-763e","template":"feature","status":"done","revision":2,"sections":{"steps":[]},"children":["warren-5f07","warren-1cae","warren-598f","warren-16f8","warren-157a","warren-a63d","warren-8dee"],"createdAt":"2026-06-13T20:39:49.468Z","updatedAt":"2026-06-14T01:08:39.526Z","name":"Polish pass","adoptedChildren":["warren-5f07","warren-1cae","warren-598f","warren-16f8","warren-157a","warren-a63d","warren-8dee"]} {"id":"pl-0008","seed":"warren-dde5","template":"feature","status":"done","revision":2,"sections":{"context":"Warren ships two top-level operator surfaces that are facets of the same object: Leveret (conversations list + /leveret/:id split-view) and Plots (list + /plots/:id three-panel detail + /plots/:id/summary). The data model already makes the Plot the durable spine and the conversation an ephemeral child of it: a conversation is bound to exactly one Plot, send-off CLOSES the conversation, and re-planning spawns a NEW conversation on the SAME Plot. On top of the redundancy, the Plot surface still renders a fully RETIRED intent-shaping path next to the live one: warren-d622 removed mode=interactive (RUN_MODES is now [batch, conversation]) and the POST /brainstorm route, yet the Plots page still shows a 'Start brainstorming' button (plotsApi.startBrainstorm -> dead POST /brainstorm) and PlotDetail still renders InteractivePanel (runsApi.createInteractive with mode:interactive, which the schema rejects) plus an orphaned Formalize (POST /plots/:id/formalize summarizing a brainstorm conversation nothing can create). Operators cannot walk one coherent flow; they bounce between /leveret/:id and /plots/:id and trip over dead buttons. This collapses both surfaces into a single cross-project Workspace and sweeps the dead brainstorm/interactive/formalize code, which also gives R-20 (colony / multi-repo) a natural top-level home to grow into.","approach":"Make the Plot the spine and the conversation a facet of it. Introduce one 'Workspace' top-level section: a single cross-project list (one row per Plot, with an active-conversation indicator) and one tabbed detail page (/workspace/:id) that walks the operator's own four-step mental model as tabs — Shape (brainstorm with Leveret: chat + dynamic intent editor + send-off), Plan (convert into plan: planner run + link out to the sd plan + sign-off gate + dispatch), Run (dispatch plan-run: children + PR-merge status + Plot auto-done), and Activity (event log + substrate + summary + past conversations). Reuse the existing sub-component trees verbatim (conversation-detail/* for Shape, plot-detail/* minus InteractivePanel for the rest, dispatch-plan-dialog for Plan, PlanRunDetail content for Run) so this is a re-composition, not a rewrite. Sweep the retired surface in the same arc: delete Formalize end to end, delete the dead UI panels, and remove the dead client methods. Old routes redirect into Workspace so no bookmark/link breaks. Sequenced server-cleanup-first so the UI client sweep compiles against an already-clean server, then list -> shell -> four tabs -> nav/routing collapse -> acceptance -> docs. Chosen over a Plot-centric merge that keeps 'Leveret' as a noun (rejected: conversation is ephemeral, a poor spine) and over a conversation-centric workspace (rejected: a Plot has N conversations over its life).","alternatives":[{"name":"Conversation-centric workspace (lead with the Leveret conversation, embed plot state)","rejected_because":"Send-off closes the conversation and re-plan spawns a new one on the same Plot, so a Plot has 1..N conversations over its life — the conversation is a poor durable spine."},{"name":"Keep two pages, only delete the dead brainstorm/formalize code","rejected_because":"Leaves the core complaint (two intertwined surfaces, no single walkable flow) unsolved."},{"name":"Rewire Formalize onto the Leveret transcript instead of deleting it","rejected_because":"Operator decision — Leveret already shapes intent live via propose_intent, so a separate summarize-then-apply step is redundant."},{"name":"Single scrollable detail page instead of tabs","rejected_because":"Operator decision — after intent is shaped, talking to Leveret does nothing, so phase tabs that go quiet/read-only model the lifecycle better than one long scroll."}],"steps":[{"title":"Server sweep — remove the orphaned Formalize endpoint. Delete POST /plots/:id/formalize from ROUTE_TABLE (src/server/handlers/index.ts), the formalizePlotHandler + brainstorm-summarize logic in src/server/handlers/plots/workbench.ts, and src/server/handlers/plots.workbench.formalize.test.ts. Regenerate docs/http-api.md (bun run gen:docs) and docs/openapi.yaml (bun run gen:openapi). Verify no remaining server caller references formalize.","type":"task","priority":2,"blocks":[3],"labels":["workspace","server","cleanup"]},{"title":"Server sweep — remove residual mode=interactive / brainstorm dispatch remnants left after warren-d622. Audit src/server/handlers/runs/ (parseRunMode + createRunHandler) and src/runs/spawn/dispatch.ts for any mode:'interactive' / interactiveAgent handling and the 'brainstorm' interactive built-in remnants; remove dead branches while PRESERVING the 'planner' interactive agent and mode:'conversation'. Remove the vestigial interactiveAgents.brainstormRuntime knob from src/warren-config/schema.ts only if it is unreferenced after the sweep. Update or trim affected tests.","type":"task","priority":2,"blocks":[3],"labels":["workspace","server","cleanup"]},{"title":"UI API-client sweep — remove the dead client methods and types: plotsApi.startBrainstorm (+ StartBrainstormInput/Response), runsApi.createInteractive, and plotsApi.formalize (+ formalize request/response types) from src/ui/src/api/client.ts and src/ui/src/api/types.ts. Fix every TypeScript reference so bun run typecheck and the src/ui tsc both pass.","type":"task","priority":2,"blocks":[4],"labels":["workspace","ui","cleanup"]},{"title":"Delete the dead UI panels. Remove src/ui/src/pages/plot-detail/interactive-panel.tsx and its usage in PlotDetail.tsx (InteractivePanel — Start brainstorming / Run planner / Formalize, all built on retired mode=interactive). Remove StartBrainstormDialog + the 'Start brainstorming' button from src/ui/src/pages/Plots.tsx. KEEP all still-live plot-detail/ sub-components (intent-panel, substrate-panel, activity-feed, header-controls, run-plan, batch-dispatch). Update affected component tests.","type":"task","priority":2,"blocks":[5],"labels":["workspace","ui","cleanup"]},{"title":"Build the Workspace list page. Add src/ui/src/pages/Workspace.tsx at a new /workspace route: a single cross-project list with one row per Plot (the durable spine), merging today's Plots list and Leveret conversations list. Columns: name, project, status/phase, intent preview, active-conversation indicator, needs-you reasons, last activity. Reuse the Plots server ?status/?filter chip contract. Actions: New Plot (existing dialog) and Start conversation (existing NewConversationButton). Backed by plotsApi.list joined with conversationsApi.list.","type":"task","priority":2,"blocks":[6],"labels":["workspace","ui"]},{"title":"Build the Workspace detail shell. Add src/ui/src/pages/WorkspaceDetail.tsx at /workspace/:id (keyed by plotId): a persistent header (PlotNameEditor, StatusTransitionControl, PlotSyncButton, project link, summary link) plus tab navigation Shape / Plan / Run / Activity with tab state in a ?tab= query param. Wire empty tab-panel child components to be filled by steps 7-10. Load the plot via plotsApi.get and reuse the existing 404/refresh-projects CTA.","type":"task","priority":2,"blocks":[7,8,9,10],"labels":["workspace","ui"]},{"title":"Implement the Shape tab. Port the live Leveret conversation surface into Shape: the streamed Chat (sendMessage override -> POST /conversations/:id/messages) on the left and the dynamic intent editor (fieldsFromIntent / patchFromFields) on the right, plus RewakeButton and the SendOff action — reusing the conversation-detail/ sub-components. Resolve the Plot's active conversation via conversationsApi.list({plot}); when none is active, render a Start-conversation affordance. After send-off the tab goes read-only/quiet.","type":"task","priority":2,"blocks":[11],"labels":["workspace","ui"]},{"title":"Implement the Plan tab (minimal). Once the merge-poller stamps conversation.plannerRunId, show the planner run status and a link out to the generated sd plan, then an operator Sign-off gate that enables the existing DispatchPlanButton (conversation-detail/dispatch-plan-dialog.tsx). No new inline plan renderer — link to the plan and dispatch over the existing /plan-runs path.","type":"task","priority":2,"blocks":[11],"labels":["workspace","ui"]},{"title":"Implement the Run tab. Embed plan-run execution for the dispatched plan: children list + per-child PR-merge status + terminal state, reusing the PlanRunDetail content, and surface the Plot's auto-done transition when the final child merges (SPEC §11.P.Plot). Resolve the plan-run from the dispatched plot/plan.","type":"task","priority":2,"blocks":[11],"labels":["workspace","ui"]},{"title":"Implement the Activity tab. Move the Plot event-log ActivityFeed (plot-detail/activity-feed.tsx) and the SubstratePanel (attachments) into the Activity tab, plus a link to the read-only Plot summary. Past/closed conversations for the Plot surface here as history entries so the full lifecycle is visible from one place.","type":"task","priority":2,"blocks":[11],"labels":["workspace","ui"]},{"title":"Collapse nav + routing. Replace the 'Leveret' and 'Plots' sidebar entries in src/ui/src/components/Layout.tsx with a single 'Workspace' entry and move the needs-you badge onto it. Register /workspace + /workspace/:id; add redirects /plots -> /workspace, /plots/:id -> /workspace/:id, /leveret -> /workspace, and /leveret/:id -> resolve conversation.plotId then redirect to /workspace/:plotId?tab=shape. Delete the now-unused page shells Leveret.tsx, Plots.tsx, ConversationDetail.tsx, PlotDetail.tsx (keeping their reused sub-component trees). Update the router.","type":"task","priority":2,"blocks":[12],"labels":["workspace","ui"]},{"title":"Update acceptance + UI tests for the Workspace flow. Adapt scenario 33 (scripts/acceptance/scenarios/33-leveret-conversation-loop.ts) and any UI tests referencing the old /leveret + /plots routes/pages to the new Workspace surface; keep the scenario deterministic, idempotent, and self-cleaning. Assert the full walk: list -> Shape (conversation + intent) -> send-off -> Plan (planner + dispatch) -> Run.","type":"task","priority":2,"blocks":[13],"labels":["workspace","acceptance"]},{"title":"Docs, gates, and bundle re-baseline. Update README + SPEC §11.O/§11.P UI references + LEVERET.md to describe the single Workspace surface and the removed brainstorm/formalize path; regen docs/http-api.md + docs/openapi.yaml; re-baseline bundle budgets (bun run check:bundle-size --update); get bun run check:all green. File a follow-up seed for the R-20 colony/multi-repo tier layered on top of Workspace.","type":"task","priority":2,"blocks":[],"labels":["workspace","docs"]}],"risks":["Route-redirect fragility: /leveret/:id is keyed by conversation id, not plot id, so the redirect must resolve conversation.plotId before sending the user to /workspace/:plotId — a naive id passthrough 404s. Covered in step 11.","Reused sub-component coupling: deleting the four page shells (step 11) while steps 7-10 still import their sub-trees (conversation-detail/*, plot-detail/*) risks breaking imports — keep the sub-component directories, only delete the page-level shells.","Server sweep over-reach: the 'planner' agent and mode:'conversation' must survive the interactive/brainstorm removal in step 2; an over-broad delete would break send-off -> planner. Preserve planner explicitly.","Bundle-size ratchet: a re-composition that changes chunking can trip check:bundle-size; re-baseline with the canonical --update (step 13), never hand-edit the budget JSON.","Plot 404-after-commit: the existing refresh-projects CTA must be carried into WorkspaceDetail (step 6) or freshly-committed Plots look broken."],"acceptance":["A single 'Workspace' top-level nav entry replaces both 'Leveret' and 'Plots'; the needs-you badge renders on it; /plots, /plots/:id, /leveret, and /leveret/:id all redirect into the Workspace surface without 404s.","/workspace shows one cross-project list keyed one-row-per-Plot with an active-conversation indicator; New Plot and Start conversation both work from it.","/workspace/:id presents Shape / Plan / Run / Activity tabs that walk the full flow: shape intent with Leveret, send off, review + sign off + dispatch the plan, watch the plan-run execute, and read activity/summary — all without leaving the page.","The retired surface is gone end to end: POST /plots/:id/formalize, InteractivePanel, StartBrainstormDialog, plotsApi.startBrainstorm, runsApi.createInteractive, and plotsApi.formalize no longer exist; no dead button reaches a removed endpoint.","bun run check:all passes (lint, typecheck, check:agents, dups, deps, size, debt, bundle-size, gen:docs:check, gen:openapi:check, coverage, ci-parity) and the updated acceptance scenario asserts the end-to-end Workspace walk.","A follow-up seed is filed for the R-20 colony / multi-repo tier on top of Workspace."]},"children":["warren-cef0","warren-d861","warren-b265","warren-af60","warren-dc54","warren-6e7d","warren-3de4","warren-e33f","warren-d17f","warren-ef97","warren-9cad","warren-1198","warren-3f94","warren-8363"],"createdAt":"2026-06-13T23:44:28.363Z","updatedAt":"2026-06-14T03:37:41.815Z","name":"Collapse Leveret + Plot into one tabbed Workspace","adoptedChildren":["warren-8363"]} -{"id":"pl-2e59","seed":"warren-faab","template":"feature","status":"approved","revision":1,"sections":{"context":"Plot plot-b11c5999 (\"Workspaces Testing\") finalized a low-risk cleanup batch capped by a 0.9.1 patch bump. Its goal names four deliverable areas: (1) the full logging/traceability hardening set, (2) workspace chat surface fixes — out-of-order turns, duplicated assistant messages, and unbounded page growth, (3) removal of the leaked .pi/sessions agent transcript from PR #340, and (4) the version bump. Area (1) is already an approved, decomposed plan: pl-f700 (parent warren-3679) with six child seeds c686 → fc6e → af76 → 26c2 → 9f06 → b2dd. A seed can only belong to one plan (plan_id is single-valued), so this plan does NOT re-adopt those six — that would re-parent them out of pl-f700 and gut the approved logging plan. Instead pl-f700 stands as the logging sub-sequence under the same Plot, and this plan covers only the net-new tail. The chat ordering/dedup defect lives in src/ui/src/components/chat-messages.ts: buildChatMessages concatenates two seq-sorted groups (transcript first, then stream) instead of merging on a single unified ordering key, so turns render transcript-first-then-stream rather than chronologically, and the (kind, content) dedupe is loose enough that a streamed assistant turn and its persisted copy can both render. The unbounded-growth defect is in src/ui/src/pages/conversation-detail/conversation-surface.tsx: the conversation Card uses min-h-[60vh] with no upper bound, so Chat.tsx's existing internal `flex-1 min-h-0 overflow-y-auto` (Chat.tsx:152/157) never engages and the /workspaces page grows with the transcript. warren-4c8d (gatewatch) documents the exact leaked file from PR #340 and is currently unplanned, so it is adopted here. The version lives in two synced places (package.json + src/index.ts VERSION) plus a CHANGELOG.md section; the release workflow fails on drift.","approach":"Walk the net-new tail serially — one seed per PR, each gated on the prior PR merging — mirroring the polish-pass convention and the Plot's stated constraint. Order: chat ordering/dedup fix → chat unbounded-growth fix → leaked-transcript removal → 0.9.1 version bump (last, capping the batch). The whole Plot batch is gated behind pl-f700: the six logging PRs land first, then this tail, so the 0.9.1 bump is the final PR after every logging + chat + transcript PR merges (the version-bump seed records that ordering since a hard cross-plan block edge to warren-b2dd would mutate a seed this run did not create). Chat fixes are split into two seeds because they touch independent files (the pure merge logic vs. the page layout) and are independently reviewable: the ordering/dedup fix is pure, unit-testable logic with a regression test added to chat-messages.test.ts; the growth fix is a layout-only change that bounds the conversation card's ancestor height to engage the existing overflow-y-auto rather than introducing a new scroll container. Transcript removal is adopted from the existing warren-4c8d rather than re-authored. All seeds pass `bun run check:all` before merge.","alternatives":[{"name":"Re-adopt the six pl-f700 logging seeds into this umbrella plan as a literal contiguous step sub-sequence","rejected_because":"A seed's plan_id is single-valued; adopting c686/fc6e/af76/26c2/9f06/b2dd here would re-parent them off the approved pl-f700, gutting that plan and its review state. The Plot already owns the logging sub-sequence via pl-f700; this plan references it for ordering instead of destroying it."},{"name":"Fix chat ordering and dedup in one seed with the unbounded-growth fix","rejected_because":"They touch different files (pure merge logic in chat-messages.ts vs. layout height in conversation-surface.tsx) and have different review surfaces and test strategies. Splitting keeps each PR small, single-concern, and serially reviewable per the batch convention."},{"name":"Introduce a new fixed-height scroll container for the workspace chat","rejected_because":"Chat.tsx already has a working internal overflow-y-auto; the page grows only because no ancestor bounds its height. Bounding the existing ancestor is the minimal fix and avoids a second competing scroll region."}],"steps":[{"title":"Fix workspace chat ordering + dedup in src/ui/src/components/chat-messages.ts: merge transcript + stream bubbles on a single unified ordering key (chronological across the transcript/stream boundary) instead of concatenating the two seq-sorted groups, and tighten buildChatMessages dedupe so a streamed assistant turn and its persisted transcript copy collapse to one bubble; add regression tests to chat-messages.test.ts","type":"bug","priority":1,"blocks":[2]},{"title":"Fix unbounded /workspaces growth: bound the conversation card's ancestor height in src/ui/src/pages/conversation-detail/conversation-surface.tsx (min-h-[60vh] → a bounded height) so Chat.tsx's existing internal flex-1 min-h-0 overflow-y-auto engages and the transcript scrolls internally instead of growing the page; no new scroll container","type":"bug","priority":2,"blocks":[3]},{"existing_seed":"warren-4c8d","blocks":[4]},{"title":"Bump version to 0.9.1: set package.json \"version\" and src/index.ts VERSION to 0.9.1 in sync (release workflow fails on drift) and add a matching CHANGELOG.md 0.9.1 section summarizing the logging hardening (pl-f700), workspace chat fixes, and leaked-transcript removal; land last, after every pl-f700 logging PR plus the chat + transcript PRs merge","type":"task","priority":1,"blocks":[]}],"risks":["The unified ordering key must be stable when transcript rows and stream events share a seq space or use disjoint seq ranges — verify the merge against a never-started anchoring run (transcript only) and an active run (both present) so neither regresses; the existing chat-messages.test.ts cases must stay green.","Tightening dedupe must not collapse legitimately repeated tool/thinking rows (they intentionally key on event id, not content) — keep that behavior while making user/agent turns collapse across transcript+stream.","Bounding the conversation card height must not clip the input row or the plot-intent column on small viewports; verify both the conversation pane and the sibling intent pane in the lg:grid-cols-2 layout.","Version-bump drift: package.json and src/index.ts must match exactly or the release workflow fails; the CHANGELOG section header must match the X.Y.Z the release job greps for.","Serial gating across two plans (pl-f700 + this one) is advisory, not a hard block edge, since wiring warren-b2dd → step 1 would mutate a seed not created in this run; the version-bump seed and umbrella seed document the ordering so dispatch respects it."],"acceptance":["On the workspace conversation surface, user and agent turns render in correct chronological order across the transcript/stream boundary (no transcript-first-then-stream reordering).","No assistant message is duplicated when a streamed turn and its persisted transcript copy both exist; the buildChatMessages dedupe is covered by a new regression test in chat-messages.test.ts.","The workspace conversation transcript scrolls internally and the /workspaces page no longer grows unbounded, using Chat.tsx's existing overflow-y-auto via a bounded ancestor height (no new scroll container).","The leaked .pi/sessions agent transcript from PR #340 is removed from the repo (warren-4c8d).","VERSION in src/index.ts and \"version\" in package.json are both 0.9.1 and in sync, with a matching CHANGELOG.md 0.9.1 section.","Every seed in this plan passes `bun run check:all` (lint, typecheck, size, debt, dups, coverage, etc.) before merge."]},"children":["warren-f4b8","warren-5755","warren-4c8d","warren-fade"],"createdAt":"2026-06-14T05:54:42.639Z","updatedAt":"2026-06-14T05:54:42.639Z","name":"Cleanup batch → 0.9.1 (net-new tail of pl-f700 logging)","adoptedChildren":["warren-4c8d"]} +{"id":"pl-2e59","seed":"warren-faab","template":"feature","status":"done","revision":1,"sections":{"context":"Plot plot-b11c5999 (\"Workspaces Testing\") finalized a low-risk cleanup batch capped by a 0.9.1 patch bump. Its goal names four deliverable areas: (1) the full logging/traceability hardening set, (2) workspace chat surface fixes — out-of-order turns, duplicated assistant messages, and unbounded page growth, (3) removal of the leaked .pi/sessions agent transcript from PR #340, and (4) the version bump. Area (1) is already an approved, decomposed plan: pl-f700 (parent warren-3679) with six child seeds c686 → fc6e → af76 → 26c2 → 9f06 → b2dd. A seed can only belong to one plan (plan_id is single-valued), so this plan does NOT re-adopt those six — that would re-parent them out of pl-f700 and gut the approved logging plan. Instead pl-f700 stands as the logging sub-sequence under the same Plot, and this plan covers only the net-new tail. The chat ordering/dedup defect lives in src/ui/src/components/chat-messages.ts: buildChatMessages concatenates two seq-sorted groups (transcript first, then stream) instead of merging on a single unified ordering key, so turns render transcript-first-then-stream rather than chronologically, and the (kind, content) dedupe is loose enough that a streamed assistant turn and its persisted copy can both render. The unbounded-growth defect is in src/ui/src/pages/conversation-detail/conversation-surface.tsx: the conversation Card uses min-h-[60vh] with no upper bound, so Chat.tsx's existing internal `flex-1 min-h-0 overflow-y-auto` (Chat.tsx:152/157) never engages and the /workspaces page grows with the transcript. warren-4c8d (gatewatch) documents the exact leaked file from PR #340 and is currently unplanned, so it is adopted here. The version lives in two synced places (package.json + src/index.ts VERSION) plus a CHANGELOG.md section; the release workflow fails on drift.","approach":"Walk the net-new tail serially — one seed per PR, each gated on the prior PR merging — mirroring the polish-pass convention and the Plot's stated constraint. Order: chat ordering/dedup fix → chat unbounded-growth fix → leaked-transcript removal → 0.9.1 version bump (last, capping the batch). The whole Plot batch is gated behind pl-f700: the six logging PRs land first, then this tail, so the 0.9.1 bump is the final PR after every logging + chat + transcript PR merges (the version-bump seed records that ordering since a hard cross-plan block edge to warren-b2dd would mutate a seed this run did not create). Chat fixes are split into two seeds because they touch independent files (the pure merge logic vs. the page layout) and are independently reviewable: the ordering/dedup fix is pure, unit-testable logic with a regression test added to chat-messages.test.ts; the growth fix is a layout-only change that bounds the conversation card's ancestor height to engage the existing overflow-y-auto rather than introducing a new scroll container. Transcript removal is adopted from the existing warren-4c8d rather than re-authored. All seeds pass `bun run check:all` before merge.","alternatives":[{"name":"Re-adopt the six pl-f700 logging seeds into this umbrella plan as a literal contiguous step sub-sequence","rejected_because":"A seed's plan_id is single-valued; adopting c686/fc6e/af76/26c2/9f06/b2dd here would re-parent them off the approved pl-f700, gutting that plan and its review state. The Plot already owns the logging sub-sequence via pl-f700; this plan references it for ordering instead of destroying it."},{"name":"Fix chat ordering and dedup in one seed with the unbounded-growth fix","rejected_because":"They touch different files (pure merge logic in chat-messages.ts vs. layout height in conversation-surface.tsx) and have different review surfaces and test strategies. Splitting keeps each PR small, single-concern, and serially reviewable per the batch convention."},{"name":"Introduce a new fixed-height scroll container for the workspace chat","rejected_because":"Chat.tsx already has a working internal overflow-y-auto; the page grows only because no ancestor bounds its height. Bounding the existing ancestor is the minimal fix and avoids a second competing scroll region."}],"steps":[{"title":"Fix workspace chat ordering + dedup in src/ui/src/components/chat-messages.ts: merge transcript + stream bubbles on a single unified ordering key (chronological across the transcript/stream boundary) instead of concatenating the two seq-sorted groups, and tighten buildChatMessages dedupe so a streamed assistant turn and its persisted transcript copy collapse to one bubble; add regression tests to chat-messages.test.ts","type":"bug","priority":1,"blocks":[2]},{"title":"Fix unbounded /workspaces growth: bound the conversation card's ancestor height in src/ui/src/pages/conversation-detail/conversation-surface.tsx (min-h-[60vh] → a bounded height) so Chat.tsx's existing internal flex-1 min-h-0 overflow-y-auto engages and the transcript scrolls internally instead of growing the page; no new scroll container","type":"bug","priority":2,"blocks":[3]},{"existing_seed":"warren-4c8d","blocks":[4]},{"title":"Bump version to 0.9.1: set package.json \"version\" and src/index.ts VERSION to 0.9.1 in sync (release workflow fails on drift) and add a matching CHANGELOG.md 0.9.1 section summarizing the logging hardening (pl-f700), workspace chat fixes, and leaked-transcript removal; land last, after every pl-f700 logging PR plus the chat + transcript PRs merge","type":"task","priority":1,"blocks":[]}],"risks":["The unified ordering key must be stable when transcript rows and stream events share a seq space or use disjoint seq ranges — verify the merge against a never-started anchoring run (transcript only) and an active run (both present) so neither regresses; the existing chat-messages.test.ts cases must stay green.","Tightening dedupe must not collapse legitimately repeated tool/thinking rows (they intentionally key on event id, not content) — keep that behavior while making user/agent turns collapse across transcript+stream.","Bounding the conversation card height must not clip the input row or the plot-intent column on small viewports; verify both the conversation pane and the sibling intent pane in the lg:grid-cols-2 layout.","Version-bump drift: package.json and src/index.ts must match exactly or the release workflow fails; the CHANGELOG section header must match the X.Y.Z the release job greps for.","Serial gating across two plans (pl-f700 + this one) is advisory, not a hard block edge, since wiring warren-b2dd → step 1 would mutate a seed not created in this run; the version-bump seed and umbrella seed document the ordering so dispatch respects it."],"acceptance":["On the workspace conversation surface, user and agent turns render in correct chronological order across the transcript/stream boundary (no transcript-first-then-stream reordering).","No assistant message is duplicated when a streamed turn and its persisted transcript copy both exist; the buildChatMessages dedupe is covered by a new regression test in chat-messages.test.ts.","The workspace conversation transcript scrolls internally and the /workspaces page no longer grows unbounded, using Chat.tsx's existing overflow-y-auto via a bounded ancestor height (no new scroll container).","The leaked .pi/sessions agent transcript from PR #340 is removed from the repo (warren-4c8d).","VERSION in src/index.ts and \"version\" in package.json are both 0.9.1 and in sync, with a matching CHANGELOG.md 0.9.1 section.","Every seed in this plan passes `bun run check:all` (lint, typecheck, size, debt, dups, coverage, etc.) before merge."]},"children":["warren-f4b8","warren-5755","warren-4c8d","warren-fade"],"createdAt":"2026-06-14T05:54:42.639Z","updatedAt":"2026-06-16T03:14:06.231Z","name":"Cleanup batch → 0.9.1 (net-new tail of pl-f700 logging)","adoptedChildren":["warren-4c8d"]} {"id":"pl-88bb","seed":"warren-60ee","template":"refactor","status":"approved","revision":1,"sections":{"context":"Constitution Article II ratchet tightening (2026-06-14 ratchetwatch patrol). src/runs/pr.ts is 659 lines — the grandfathered file furthest over the 500-line global limit (scripts/file-size-budgets.json: \"src/runs/pr.ts\": 659) and not covered by any open seed. Grandfather entries are silent debt; the one-decomposition-per-patrol slot retires this one so the budget entry can be removed (ratchet only goes down).","behavior_invariant":"Every symbol src/runs/pr.ts exports today keeps the same name, signature, and runtime behavior, importable from the same module path src/runs/pr.ts (re-export from pr.ts if a symbol physically moves to a sibling). Current importers — src/server/main/index.ts, src/server/handlers/plots/attachments.ts, src/plots/sync.ts, src/plots/pr-merger.ts, src/plan-runs/pr-merge.ts, src/runs/pr-annotate.ts, src/runs/index.ts, src/runs/reap/run.ts, src/runs/reap/pr-open.ts, src/runs/reap/types.ts, src/runs/pr-template.ts — compile and pass unchanged. The full src/runs/pr.test.ts suite stays green with the same test count.","approach":"Split along the existing seam: move the PR-merge / URL-parsing group (checkPullRequestMerged, parsePullRequestUrl, parsePullRequestRef, mergePullRequest, isRateLimited, plus their CheckPullRequestMergedInput/CheckPrMergedResult/MergePullRequestInput/MergePullRequestResult types and PR_URL_RE/PR_SHORT_RE regexes — roughly lines 403-659) into a new sibling module src/runs/pr-checks.ts (do NOT reuse the name pr-merge.ts; src/plan-runs/pr-merge.ts already exists). Re-export the moved public symbols from src/runs/pr.ts so the module path is unchanged for every importer. That cut removes ~250 lines, landing pr.ts near ~405 lines — comfortably under 500. Keep openPullRequest, buildPrContent, loadAutoOpenPrConfigFromEnv and friends in pr.ts.","steps":[{"title":"Create src/runs/pr-checks.ts and move the PR-merge/URL-parse group into it. Move checkPullRequestMerged, mergePullRequest, parsePullRequestUrl, parsePullRequestRef, isRateLimited and their associated exported types/regexes (CheckPullRequestMergedInput, CheckPrMergedResult, MergePullRequestInput, MergePullRequestResult, PR_URL_RE, PR_SHORT_RE) out of src/runs/pr.ts into the new src/runs/pr-checks.ts, carrying any private helpers they need (buildHeaders/readJson/readText/truncate may need to be shared — if so, keep one copy and import it, do NOT duplicate, or jscpd check:dups will flag it). Re-export every moved PUBLIC symbol from src/runs/pr.ts (export { ... } from \"./pr-checks.ts\") so the path src/runs/pr.ts still resolves all of them. Verify: `wc -l src/runs/pr.ts` shows < 500 AND `bun run typecheck` is clean AND `bun test src/runs/pr.test.ts` is green with the same test count as before the split.","labels":["ratchetwatch"]},{"title":"Remove the \"src/runs/pr.ts\": 659 entry from scripts/file-size-budgets.json (delete that one line). Do NOT add a budget entry for src/runs/pr-checks.ts — it must default-pass under the 500 threshold (confirm `wc -l src/runs/pr-checks.ts` < 500). Per Constitution Article VI, before declaring done run a repo-wide search for any reference to the old single-file assumption and the new path across ALL file types — `rg -n \"runs/pr\\\\.ts|runs/pr-checks\" --hidden -g '!node_modules'` plus an explicit sweep of Dockerfile, docker-compose.yml, .github/workflows/*.yml, src/supervisor/ spawn/config strings, and docs/ — and fix any stale reference (file moves have broken production here before; encode the check, do not assume it). Verify: `bun run check:size` exits 0 AND `bun run check:dups` exits 0 AND `bun run check:all` is fully green (every gate stays green, the raised-tightness budget still passes against current actuals).","labels":["ratchetwatch"],"blocks":[]}],"acceptance":["src/runs/pr.ts is < 500 lines and its budget entry is gone from scripts/file-size-budgets.json; src/runs/pr-checks.ts is < 500 lines with no budget entry.","All prior importers compile unchanged and `bun run typecheck` is clean — every public symbol is still importable from src/runs/pr.ts.","`bun test src/runs/pr.test.ts` passes with the same test count as before the split (behavior invariant preserved).","Article VI repo-wide old-path/new-path search (rg + Dockerfile/compose/workflow YAML/supervisor strings/docs) shows no stale or broken references.","`bun run check:all` is fully green — no gate regresses and no release step is added (Article III: hygiene batches into the next real release)."]},"children":["warren-db9a","warren-70d7"],"createdAt":"2026-06-14T10:46:18.235Z","updatedAt":"2026-06-14T10:46:18.235Z","name":"Decompose src/runs/pr.ts below the 500-line limit"} {"id":"pl-ef08","seed":"warren-fa85","template":"refactor","status":"done","revision":1,"sections":{"context":"Constitution Article II ratchet tightening (2026-06-15 ratchetwatch patrol). scripts/acceptance/scenarios/20-preview.ts is 658 lines — grandfathered at \"scripts/acceptance/scenarios/20-preview.ts\": 658 in scripts/file-size-budgets.json. src/runs/pr.ts (659, furthest over the 500-line limit) is already covered by open plan pl-88bb (warren-db9a/warren-70d7), so 20-preview.ts is the furthest-over grandfathered file NOT covered by an open seed and takes this patrol's single one-decomposition-per-patrol slot. Retiring it lets the budget entry be removed (ratchet only goes down).","behavior_invariant":"The acceptance scenario keeps the exact same `scenario` export (id 20, both variants A happy-path + B idle-TTL eviction) importable from the unchanged path scripts/acceptance/scenarios/20-preview.ts. scripts/acceptance/run.ts line `import { scenario as scenario20 } from \"./scenarios/20-preview.ts\";` stays byte-identical and resolves. runVariantA/runVariantB behavior is unchanged. Scenario 20 still passes (or skips with its documented non-Linux guard) exactly as before. No other scenario imports these helpers (20-preview-path.ts does NOT import from 20-preview.ts — verified), so the move is file-local.","approach":"Mirror the established precedent scripts/acceptance/scenarios/32-plot-workbench-loop.helpers.ts: extract the file-local test-helper group into a sibling `scripts/acceptance/scenarios/20-preview.helpers.ts` and import it back. Move the contiguous helper block (roughly lines 390-658): buildPreviewProjectFixture, commitInSource, runGit, ensureProject, waitForRunTerminal, waitForPreviewState, fetchEvents, proxyRequest, loginAndIssueCookie, parseSetCookie, sleep, plus their helper-local types BuildFixtureInput, BuiltPreviewFixture, ProxyRequestInput, ProxyResponse, LoginInput. The shared row types used by BOTH the scenario body and the helpers (ProjectRow used by ensureProject, EventRow used by fetchEvents) move to the helpers file and are re-imported into 20-preview.ts (same pattern as RunRow in 32's helpers) — do NOT duplicate a type in both files or jscpd check:dups will flag it. Keep `scenario`, runVariantA, runVariantB, and the file header comment in 20-preview.ts. Removing ~268 lines lands 20-preview.ts near ~390 lines; the new helpers file is ~270 lines — both comfortably under 500. Discovery is import-by-name in run.ts (no glob), so a `.helpers.ts` sibling is never auto-loaded as a scenario.","steps":[{"title":"Create scripts/acceptance/scenarios/20-preview.helpers.ts and move the file-local helper group into it. Move buildPreviewProjectFixture, commitInSource, runGit, ensureProject, waitForRunTerminal, waitForPreviewState, fetchEvents, proxyRequest, loginAndIssueCookie, parseSetCookie, sleep, plus their helper-local types (BuildFixtureInput, BuiltPreviewFixture, ProxyRequestInput, ProxyResponse, LoginInput) and the shared row types they need (ProjectRow, EventRow), out of scripts/acceptance/scenarios/20-preview.ts into the new sibling file, exporting each symbol the scenario body still references. Carry the imports those helpers need (node:crypto/node:fs/promises/node:os/node:path, ../lib/assert.ts, ../lib/http.ts WarrenHttp, ../lib/inproc.ts) into the helpers file; if a private utility is needed by both files keep ONE copy and import it (do NOT duplicate, or jscpd check:dups will flag it). Import the moved symbols back into 20-preview.ts. Keep the `scenario` export, runVariantA, runVariantB, and the header comment in 20-preview.ts unchanged. Mirror the existing precedent scripts/acceptance/scenarios/32-plot-workbench-loop.helpers.ts. Verify: `wc -l scripts/acceptance/scenarios/20-preview.ts` shows < 500 AND `wc -l scripts/acceptance/scenarios/20-preview.helpers.ts` shows < 500 AND `bun run typecheck` is clean (run.ts still resolves the unchanged `scenario as scenario20` import).","labels":["ratchetwatch"]},{"title":"Remove the \"scripts/acceptance/scenarios/20-preview.ts\": 658 entry from scripts/file-size-budgets.json (delete that one line). Do NOT add a budget entry for scripts/acceptance/scenarios/20-preview.helpers.ts — it must default-pass under the 500 threshold (confirm `wc -l scripts/acceptance/scenarios/20-preview.helpers.ts` < 500). Per Constitution Article VI, before declaring done run a repo-wide search for any reference to the old single-file assumption and the new path across ALL file types — `rg -n \"20-preview\\\\.ts|20-preview\\\\.helpers|scenarios/20-preview\" --hidden -g '!node_modules'` plus an explicit sweep of scripts/acceptance/run.ts, Dockerfile, docker-compose.yml, .github/workflows/*.yml, src/supervisor/ spawn/config strings, and docs/ — and fix any stale reference (file moves have broken production here before; encode the check, do not assume it). Verify: `bun run check:size` exits 0 AND `bun run check:dups` exits 0 AND `bun run check:all` is fully green (every gate stays green; the removed entry leaves both resulting files default-passing under the 500-line threshold).","labels":["ratchetwatch"],"blocks":[]}],"acceptance":["scripts/acceptance/scenarios/20-preview.ts is < 500 lines and its 658-line budget entry is gone from scripts/file-size-budgets.json; scripts/acceptance/scenarios/20-preview.helpers.ts is < 500 lines with NO budget entry (default-passes under threshold).","The `scenario` export and scripts/acceptance/run.ts `import { scenario as scenario20 } from \"./scenarios/20-preview.ts\"` are unchanged and resolve; `bun run typecheck` is clean.","No other scenario or file references a stale path — the Article VI repo-wide rg sweep (plus Dockerfile/compose/workflow YAML/supervisor strings/docs) shows no broken or stale reference.","`bun run check:size` and `bun run check:dups` exit 0 (no duplicated helper/type across the two files).","`bun run check:all` is fully green — no gate regresses and no release step is added (Article III: hygiene batches into the next real release)."]},"children":["warren-65f6","warren-0e37"],"createdAt":"2026-06-15T10:46:06.270Z","updatedAt":"2026-06-15T10:58:43.131Z","name":"Decompose scripts/acceptance/scenarios/20-preview.ts below the 500-line limit"} From c8eca733d67f2e0232d976e1ffa4611ee78cf6bc Mon Sep 17 00:00:00 2001 From: warren Date: Tue, 16 Jun 2026 03:14:22 +0000 Subject: [PATCH 3/4] chore(warren): plot state --- .plot/plot-b11c5999.events.jsonl | 1 + 1 file changed, 1 insertion(+) diff --git a/.plot/plot-b11c5999.events.jsonl b/.plot/plot-b11c5999.events.jsonl index 39d685db..009f3873 100644 --- a/.plot/plot-b11c5999.events.jsonl +++ b/.plot/plot-b11c5999.events.jsonl @@ -13,3 +13,4 @@ {"type":"run_dispatched","actor":"user:operator","at":"2026-06-16T02:47:02.972Z","data":{"run_id":"run_mqpjexh9zf3c","agent":"pi","model":"claude-opus-4-8","project":"prj_z07y6ssfjfpk"}} {"type":"run_dispatched","actor":"user:operator","at":"2026-06-16T02:57:44.243Z","data":{"run_id":"run_90fqekdeztrt","agent":"pi","model":"claude-opus-4-8","project":"prj_z07y6ssfjfpk"}} {"type":"run_dispatched","actor":"user:operator","at":"2026-06-16T03:05:13.368Z","data":{"run_id":"run_jq3fqgwcs8bb","agent":"pi","model":"claude-opus-4-8","project":"prj_z07y6ssfjfpk"}} +{"type":"run_dispatched","actor":"user:operator","at":"2026-06-16T03:10:33.438Z","data":{"run_id":"run_gky38mc6b49z","agent":"pi","model":"claude-opus-4-8","project":"prj_z07y6ssfjfpk"}} From c08830a221ec24c8f47764de5d7f42f2e2180da8 Mon Sep 17 00:00:00 2001 From: warren Date: Tue, 16 Jun 2026 03:14:22 +0000 Subject: [PATCH 4/4] chore(warren): seeds state --- .seeds/issues.jsonl | 11 +++++++++-- .seeds/plans.jsonl | 3 ++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.seeds/issues.jsonl b/.seeds/issues.jsonl index 3e80b970..20c3d7d7 100644 --- a/.seeds/issues.jsonl +++ b/.seeds/issues.jsonl @@ -722,10 +722,10 @@ {"id":"warren-6060","title":"canopy: typed frontmatter via --fm key:=value JSON syntax (cn --fm stringifies all values, breaking boolean flags like auto_plan_run — warren-5f07 worked around it with defensive coercion)","status":"open","type":"feature","priority":2,"createdAt":"2026-06-14T00:01:28.201Z","updatedAt":"2026-06-14T00:01:28.201Z"} {"id":"warren-8363","title":"Release — minor version bump via .claude/commands/release.md flow","status":"closed","type":"task","priority":2,"createdAt":"2026-06-14T01:37:50.516Z","updatedAt":"2026-06-14T03:37:41.815Z","description":"\nAdopted into plan pl-0008.\n\nParent seed: warren-dde5 — Collapse Leveret + Plot into a single tabbed Workspace surface\nPlan template: feature\nPlan approach: Make the Plot the spine and the conversation a facet of it. Introduce one 'Workspace' top-level section: a single cross-project list (one row per Plot, with an active-conversation indicator) and one tabbed detail page (/workspace/:id) that…\n\nRun `sd plan show pl-0008` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n\n\nAfter the Workspace collapse lands (steps 1-13, ending warren-3f94 with check:all green), cut a release by running the .claude/commands/release.md flow with an explicit MINOR bump: review git log since the last tag, bump the version in BOTH package.json (\"version\") and src/index.ts (export const VERSION) to the next minor (X.Y+1.0 -> e.g. patch/minor reset), move CHANGELOG.md [Unreleased] items into the new dated version section, update CLAUDE.md/README.md if command counts or structure changed, then commit. The release workflow (.github/workflows/release.yml) fails if package.json and src/index.ts disagree, so keep them in sync. This is a minor (not patch) bump because the Workspace collapse is a user-facing surface change (Leveret + Plots merged into one tabbed Workspace, brainstorm/formalize path removed).","plan_id":"pl-0008","blocks":["warren-dde5"],"closedAt":"2026-06-14T03:37:41.815Z"} {"id":"warren-8436","title":"R-20: colony / multi-repo tier layered on top of the Workspace surface","status":"open","type":"feature","priority":3,"createdAt":"2026-06-14T03:27:15.092Z","updatedAt":"2026-06-14T03:27:30.187Z","labels":["roadmap"],"description":"Layer a multi-repo / colony shaping tier on top of the single Workspace surface landed by pl-0008 (warren-dde5). The Workspace list (one row per Plot, cross-project spine) and the /workspace/:id tabbed detail (Shape -> Plan -> Run -> Activity) are the designated home for R-20: extend the list to span multiple repos/colonies, let a single Plot fan shaping + plan-runs across repos, and surface colony-level status. Builds directly on src/ui/src/pages/Workspace.tsx + WorkspaceDetail.tsx and the SPEC 11.O.Plot.UI Workspace note."} -{"id":"warren-faab","title":"Cleanup batch → 0.9.1: logging hardening (pl-f700) + workspace chat fixes + leaked transcript removal + version bump","status":"open","type":"epic","priority":1,"createdAt":"2026-06-14T05:53:53.355Z","updatedAt":"2026-06-16T03:14:06.231Z","description":"Umbrella seed for Plot plot-b11c5999 (\"Workspaces Testing\"). Low-risk cleanup batch capped by a 0.9.1 patch bump. Logging/traceability hardening is the already-approved plan pl-f700 (6 seeds: warren-c686 → warren-fc6e → warren-af76 → warren-26c2 → warren-9f06 → warren-b2dd) — adopted as a contiguous sub-sequence, NOT re-authored. This seed's plan covers the net-new tail: workspace chat ordering/dedup fix, workspace chat unbounded-growth fix, leaked .pi/sessions transcript removal (warren-4c8d), and the 0.9.1 version bump. Each seed lands as its own PR, walked serially, gated on the prior PR merging; version bump lands last after every logging + chat + transcript PR merges. All work passes 'bun run check:all'.","plan_id":"pl-2e59","blockedBy":["warren-4c8d"]} +{"id":"warren-faab","title":"Cleanup batch → 0.9.1: logging hardening (pl-f700) + workspace chat fixes + leaked transcript removal + version bump","status":"open","type":"epic","priority":1,"createdAt":"2026-06-14T05:53:53.355Z","updatedAt":"2026-06-16T03:14:22.656Z","description":"Umbrella seed for Plot plot-b11c5999 (\"Workspaces Testing\"). Low-risk cleanup batch capped by a 0.9.1 patch bump. Logging/traceability hardening is the already-approved plan pl-f700 (6 seeds: warren-c686 → warren-fc6e → warren-af76 → warren-26c2 → warren-9f06 → warren-b2dd) — adopted as a contiguous sub-sequence, NOT re-authored. This seed's plan covers the net-new tail: workspace chat ordering/dedup fix, workspace chat unbounded-growth fix, leaked .pi/sessions transcript removal (warren-4c8d), and the 0.9.1 version bump. Each seed lands as its own PR, walked serially, gated on the prior PR merging; version bump lands last after every logging + chat + transcript PR merges. All work passes 'bun run check:all'.","plan_id":"pl-2e59","blockedBy":["warren-4c8d"]} {"id":"warren-f4b8","title":"Fix workspace chat ordering + dedup in src/ui/src/components/chat-messages.ts: merge transcript + stream bubbles on a single unified ordering key (chronological across the transcript/stream boundary) instead of concatenating the two seq-sorted groups, and tighten buildChatMessages dedupe so a streamed assistant turn and its persisted transcript copy collapse to one bubble; add regression tests to chat-messages.test.ts","status":"closed","type":"bug","priority":1,"plan_step_index":0,"description":"\nStep 1 of plan pl-2e59.\n\nParent seed: warren-faab — Cleanup batch → 0.9.1: logging hardening (pl-f700) + workspace chat fixes + leaked transcript removal + version bump\nPlan template: feature\nPlan approach: Walk the net-new tail serially — one seed per PR, each gated on the prior PR merging — mirroring the polish-pass convention and the Plot's stated constraint. Order: chat ordering/dedup fix → chat unbounded-growth fix → leaked-transcript…\n\nRun `sd plan show pl-2e59` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n","createdAt":"2026-06-14T05:54:42.639Z","updatedAt":"2026-06-16T02:52:31.503Z","plan_id":"pl-2e59","blocks":["warren-5755","warren-faab"],"closedAt":"2026-06-16T02:52:31.503Z"} {"id":"warren-5755","title":"Fix unbounded /workspaces growth: bound the conversation card's ancestor height in src/ui/src/pages/conversation-detail/conversation-surface.tsx (min-h-[60vh] → a bounded height) so Chat.tsx's existing internal flex-1 min-h-0 overflow-y-auto engages and the transcript scrolls internally instead of growing the page; no new scroll container","status":"closed","type":"bug","priority":2,"plan_step_index":1,"description":"\nStep 2 of plan pl-2e59.\n\nParent seed: warren-faab — Cleanup batch → 0.9.1: logging hardening (pl-f700) + workspace chat fixes + leaked transcript removal + version bump\nPlan template: feature\nPlan approach: Walk the net-new tail serially — one seed per PR, each gated on the prior PR merging — mirroring the polish-pass convention and the Plot's stated constraint. Order: chat ordering/dedup fix → chat unbounded-growth fix → leaked-transcript…\n\nRun `sd plan show pl-2e59` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n","createdAt":"2026-06-14T05:54:42.639Z","updatedAt":"2026-06-16T03:03:49.017Z","plan_id":"pl-2e59","blocks":["warren-4c8d","warren-faab"],"extensions":{"role":"pi","lastRunId":"run_90fqekdeztrt","lastRunAt":"2026-06-16T02:57:44.047Z"},"closedAt":"2026-06-16T03:03:49.017Z"} -{"id":"warren-fade","title":"Bump version to 0.9.1: set package.json \"version\" and src/index.ts VERSION to 0.9.1 in sync (release workflow fails on drift) and add a matching CHANGELOG.md 0.9.1 section summarizing the logging hardening (pl-f700), workspace chat fixes, and leaked-transcript removal; land last, after every pl-f700 logging PR plus the chat + transcript PRs merge","status":"closed","type":"task","priority":1,"plan_step_index":3,"description":"\nStep 4 of plan pl-2e59.\n\nParent seed: warren-faab — Cleanup batch → 0.9.1: logging hardening (pl-f700) + workspace chat fixes + leaked transcript removal + version bump\nPlan template: feature\nPlan approach: Walk the net-new tail serially — one seed per PR, each gated on the prior PR merging — mirroring the polish-pass convention and the Plot's stated constraint. Order: chat ordering/dedup fix → chat unbounded-growth fix → leaked-transcript…\n\nRun `sd plan show pl-2e59` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n","createdAt":"2026-06-14T05:54:42.639Z","updatedAt":"2026-06-16T03:14:06.231Z","plan_id":"pl-2e59","blockedBy":["warren-4c8d"],"blocks":["warren-faab"],"closedAt":"2026-06-16T03:14:06.231Z"} +{"id":"warren-fade","title":"Bump version to 0.9.1: set package.json \"version\" and src/index.ts VERSION to 0.9.1 in sync (release workflow fails on drift) and add a matching CHANGELOG.md 0.9.1 section summarizing the logging hardening (pl-f700), workspace chat fixes, and leaked-transcript removal; land last, after every pl-f700 logging PR plus the chat + transcript PRs merge","status":"closed","type":"task","priority":1,"plan_step_index":3,"description":"\nStep 4 of plan pl-2e59.\n\nParent seed: warren-faab — Cleanup batch → 0.9.1: logging hardening (pl-f700) + workspace chat fixes + leaked transcript removal + version bump\nPlan template: feature\nPlan approach: Walk the net-new tail serially — one seed per PR, each gated on the prior PR merging — mirroring the polish-pass convention and the Plot's stated constraint. Order: chat ordering/dedup fix → chat unbounded-growth fix → leaked-transcript…\n\nRun `sd plan show pl-2e59` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n","createdAt":"2026-06-14T05:54:42.639Z","updatedAt":"2026-06-16T03:14:22.656Z","plan_id":"pl-2e59","blockedBy":["warren-4c8d"],"blocks":["warren-faab"],"closedAt":"2026-06-16T03:14:22.656Z"} {"id":"warren-d426","title":"gatewatch: 4 Article IX-protected merges in window lack human sign-off record","status":"open","type":"task","priority":1,"createdAt":"2026-06-14T10:15:36.224Z","updatedAt":"2026-06-14T10:15:36.224Z","description":"Constitution Article IX requires that any merged change to docs/CONSTITUTION.md, the gatewatch/ratchetwatch/tastewatch entries in .canopy/, or audit entries in .warren/triggers.yaml carry EXPLICIT human review, recorded in the seed tracker (the warren-ea20 ratification-record pattern: 'explicitly human-reviewed and human-merged by the operator... This seed is the sign-off evidence gatewatch's Article IX check looks for'). Four PRs merged in the last 36h touch protected paths and NONE has a corresponding ratification/sign-off seed. The auto-merge.yml Article IX gate (intact, untouched in window) would have BLOCKED auto-merge on these, implying a human merge occurred — but with no recorded ratification, gatewatch cannot verify the gate was honest. Per gatewatch mandate item 6, file priority 1.\n\nEVIDENCE (each modifies its own mandate / the constitution):\n- SHA 7d3cf95e (PR #364): docs/CONSTITUTION.md — adds the 'Audit Warden boundary' section (ingestion rule, digest cadence, meta-Plot, autonomy-promotion constraints). Closes warren-05ef (a WORK seed, not a sign-off record).\n- SHA e2d88409 (PR #362): .warren/triggers.yaml — adds the 'warden-digest' cron audit entry (0 5 * * 0). The constitution explicitly names warden-digest as Article IX-protected. Closes warren-4240 (work seed).\n- SHA f8ead2ff (PR #361): .canopy/prompts.jsonl — bumps gatewatch (v2), ratchetwatch (v2), tastewatch (v3) auditor system prompts to add warden-delivery instructions; ALSO edits the three audit prompt entries in .warren/triggers.yaml. This is the audit population editing its own running mandate (including this very gatewatch prompt). Work seed warren-7f62, not a ratification record.\n- SHA 5f41fef5 (PR #338): .canopy/prompts.jsonl — repins the tastewatch agent model claude-fable-5 -> claude-opus-4-8 (tastewatch entry v1 -> v2). Modifies a protected auditor .canopy entry. No ratification record. (Same PR also adds a biome.json formatter-exempt override carrying tracker pl-cf2a — Article II satisfied there; the Article IX gap is the .canopy auditor change.)\n\nWHAT ARTICLE IX REQUIRES: a recorded human sign-off (warren-ea20-style) per protected-path merge, OR revert. The gate forcing human-merge is necessary but not sufficient — the constitution treats the recorded ratification seed as the audit evidence.\n\nREMEDIATION IS NON-MECHANICAL (no plan attached): the fix is a human decision — file a ratification record for each PR (#364, #362, #361, #338) attesting the operator reviewed and merged it, or revert. A mechanical plan-run cannot self-grant this, and any plan touching these protected paths would itself re-trigger Article IX. Routed to the warden for human triage.\n\nDedupe: not covered by warren-d5e5 (PR #270), warren-4c8d (PR #340), or warren-ea20 (PR #337 only). warden delivery: undeliverable this patrol (WARREN_API_TOKEN unset -> /conversations 401); seed is the durable record.","labels":["audit","gatewatch"]} {"id":"warren-60ee","title":"ratchetwatch tightening: 2026-06-14","status":"open","type":"task","priority":3,"createdAt":"2026-06-14T10:45:24.898Z","updatedAt":"2026-06-14T10:46:18.235Z","description":"Mechanical ratchet tightening for the 2026-06-14 patrol (Constitution Article II — ratchets only tighten).\n\nMeasurements (clean env; node_modules repaired via bun install --frozen-lockfile):\n- Coverage: functions 88.90% vs floor 88.60% (slack 0.30pt); lines 91.65% vs floor 91.54% (slack 0.11pt). Both < 0.75pt → no floor raise this patrol.\n- File-size: no grandfathered entry has dropped below the 500-line global limit (smallest = src/plots/aggregate.ts at 506), so no satisfied entries to remove.\n- Bundle: 7-day net gzip-js -221 B (re-baseline-down in #384); raises sum ~1.9 KB << 20 KB → no finding.\n- Debt allowlist: empty → no finding.\n\nThe single mechanical tightening this patrol is the one-file-per-patrol grandfather decomposition: src/runs/pr.ts (659 lines, furthest over the 500 limit, not covered by any open seed). See the attached refactor plan. Per Article III no release step is included — this hygiene batches into the next real release.","labels":["audit","ratchetwatch"],"plan_id":"pl-88bb","blockedBy":["warren-db9a","warren-70d7"]} {"id":"warren-889a","title":"Grandfather-at-birth: src/runs/stream/bridge.test.ts pushed to 521 lines and exempted in be18ba73 (within 24h)","status":"open","type":"task","priority":3,"createdAt":"2026-06-14T10:45:36.305Z","updatedAt":"2026-06-14T10:45:36.305Z","description":"Constitution Article II finding (ratchets only tighten / nothing grandfathered at birth).\n\nEvidence (Article VIII):\n- File: src/runs/stream/bridge.test.ts — current 521 lines (limit 500).\n- Budget entry \"src/runs/stream/bridge.test.ts\": 521 ADDED in commit be18ba73e20fd4f3beee5cdb7e0563ef6b90cb6a (Jaymin West, 2026-06-13 11:51:08 -0700 = 18:51 UTC; tracker warren-df71, 'feat(runs): keep conversation runs alive...'), i.e. within the trailing 24h of this 2026-06-14 10:42 UTC patrol.\n- The file pre-existed (created 2026-05-27 in 22d16f5d) and was previously UNDER budget; warren-df71 grew it past 500 and added a fresh grandfather exception in the same diff rather than keeping the test under the limit (Article II: 'a new file written over the size limit is decomposed before merge, not exempted at write time' — same spirit applies to pushing a compliant file over the line).\n\nSeverity is low: only 21 lines over. Remedy is a small split/trim of bridge.test.ts back under 500 and removal of its budget entry — NOT planned by ratchetwatch this patrol because the one-decomposition-per-patrol slot went to the furthest-over file (src/runs/pr.ts, 659). Filed for human/gatewatch attention.\n\nDedupe: no existing seed matches 'bridge.test' or 'df71' (searched). Coordinate with gatewatch on be18ba73/warren-df71 rather than double-filing.","labels":["audit","ratchetwatch"]} @@ -738,3 +738,10 @@ {"id":"warren-fa85","title":"ratchetwatch tightening: 2026-06-15","status":"open","type":"task","priority":3,"createdAt":"2026-06-15T10:45:30.233Z","updatedAt":"2026-06-15T10:58:43.131Z","description":"Mechanical ratchet tightening for the 2026-06-15 patrol (Constitution Article II — ratchets only tighten).\n\nMeasurements (clean env; node_modules repaired via `bun install --frozen-lockfile` before measuring — local checkout shipped without zod/@os-eco/burrow-cli, which depresses coverage if not repaired):\n- Coverage (`bun run check:coverage`): functions 88.90% vs floor 88.60% (slack 0.30pt); lines 91.65% vs floor 91.54% (slack 0.11pt). Both < 0.75pt → NO floor raise this patrol.\n- File-size grandfather list: no entry has dropped below the 500-line global limit (smallest = src/plots/aggregate.ts at 506), so no satisfied entries to remove. No file-size-budgets.json change in the last 24h → no grandfather-at-birth finding.\n- Bundle creep: 7-day gzip-js raises sum ~1.7KB (525+569+580 B across #380/#381 Plan/Run/Activity tabs), net NEGATIVE after the #384 re-baseline-down (304059 < 304567 start-of-week). << 20KB → no finding.\n- Debt-marker allowlist: empty → no finding.\n\nThe single mechanical tightening this patrol is the one-file-per-patrol grandfather decomposition: scripts/acceptance/scenarios/20-preview.ts (658 lines). src/runs/pr.ts (659, furthest over) is already covered by open plan pl-88bb (warren-db9a/warren-70d7), so 20-preview.ts is the furthest-over file NOT covered by an open seed. See the attached refactor plan. Per Article III no release step is included — hygiene batches into the next real release.\n\nwarden: undeliverable (WARREN_API_TOKEN unset; conversation 'Audit Warden' could not be authenticated/resolved). Seed/plan is the durable record.","labels":["audit","ratchetwatch"],"plan_id":"pl-ef08"} {"id":"warren-65f6","title":"Create scripts/acceptance/scenarios/20-preview.helpers.ts and move the file-local helper group into it. Move buildPreviewProjectFixture, commitInSource, runGit, ensureProject, waitForRunTerminal, waitForPreviewState, fetchEvents, proxyRequest, loginAndIssueCookie, parseSetCookie, sleep, plus their helper-local types (BuildFixtureInput, BuiltPreviewFixture, ProxyRequestInput, ProxyResponse, LoginInput) and the shared row types they need (ProjectRow, EventRow), out of scripts/acceptance/scenarios/20-preview.ts into the new sibling file, exporting each symbol the scenario body still references. Carry the imports those helpers need (node:crypto/node:fs/promises/node:os/node:path, ../lib/assert.ts, ../lib/http.ts WarrenHttp, ../lib/inproc.ts) into the helpers file; if a private utility is needed by both files keep ONE copy and import it (do NOT duplicate, or jscpd check:dups will flag it). Import the moved symbols back into 20-preview.ts. Keep the `scenario` export, runVariantA, runVariantB, and the header comment in 20-preview.ts unchanged. Mirror the existing precedent scripts/acceptance/scenarios/32-plot-workbench-loop.helpers.ts. Verify: `wc -l scripts/acceptance/scenarios/20-preview.ts` shows < 500 AND `wc -l scripts/acceptance/scenarios/20-preview.helpers.ts` shows < 500 AND `bun run typecheck` is clean (run.ts still resolves the unchanged `scenario as scenario20` import).","status":"closed","type":"task","priority":2,"plan_step_index":0,"description":"\nStep 1 of plan pl-ef08.\n\nParent seed: warren-fa85 — ratchetwatch tightening: 2026-06-15\nPlan template: refactor\nPlan approach: Mirror the established precedent scripts/acceptance/scenarios/32-plot-workbench-loop.helpers.ts: extract the file-local test-helper group into a sibling `scripts/acceptance/scenarios/20-preview.helpers.ts` and import it back. Move the…\n\nRun `sd plan show pl-ef08` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n","createdAt":"2026-06-15T10:46:06.270Z","updatedAt":"2026-06-15T10:53:39.566Z","labels":["ratchetwatch"],"plan_id":"pl-ef08","blocks":["warren-fa85"],"extensions":{"role":"pi","lastRunId":"run_b2q7z3p7131f","lastRunAt":"2026-06-15T10:48:09.609Z"},"closedAt":"2026-06-15T10:53:39.566Z"} {"id":"warren-0e37","title":"Remove the \"scripts/acceptance/scenarios/20-preview.ts\": 658 entry from scripts/file-size-budgets.json (delete that one line). Do NOT add a budget entry for scripts/acceptance/scenarios/20-preview.helpers.ts — it must default-pass under the 500 threshold (confirm `wc -l scripts/acceptance/scenarios/20-preview.helpers.ts` < 500). Per Constitution Article VI, before declaring done run a repo-wide search for any reference to the old single-file assumption and the new path across ALL file types — `rg -n \"20-preview\\\\.ts|20-preview\\\\.helpers|scenarios/20-preview\" --hidden -g '!node_modules'` plus an explicit sweep of scripts/acceptance/run.ts, Dockerfile, docker-compose.yml, .github/workflows/*.yml, src/supervisor/ spawn/config strings, and docs/ — and fix any stale reference (file moves have broken production here before; encode the check, do not assume it). Verify: `bun run check:size` exits 0 AND `bun run check:dups` exits 0 AND `bun run check:all` is fully green (every gate stays green; the removed entry leaves both resulting files default-passing under the 500-line threshold).","status":"closed","type":"task","priority":2,"plan_step_index":1,"description":"\nStep 2 of plan pl-ef08.\n\nParent seed: warren-fa85 — ratchetwatch tightening: 2026-06-15\nPlan template: refactor\nPlan approach: Mirror the established precedent scripts/acceptance/scenarios/32-plot-workbench-loop.helpers.ts: extract the file-local test-helper group into a sibling `scripts/acceptance/scenarios/20-preview.helpers.ts` and import it back. Move the…\n\nRun `sd plan show pl-ef08` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n","createdAt":"2026-06-15T10:46:06.270Z","updatedAt":"2026-06-15T10:58:43.131Z","labels":["ratchetwatch"],"plan_id":"pl-ef08","blocks":["warren-fa85"],"closedAt":"2026-06-15T10:58:43.131Z","closeReason":"Removed the 658-line grandfather entry for scripts/acceptance/scenarios/20-preview.ts from scripts/file-size-budgets.json. Both resulting files default-pass under 500 (20-preview.ts=368, 20-preview.helpers.ts=320). Repo-wide sweep (run.ts, Dockerfile, docker-compose.yml, workflows, src/supervisor, docs) found no stale single-file references; run.ts still resolves scenario20. check:size, check:dups, and full check:all (12/12) all green."} +{"id":"warren-832d","title":"UI Nits: consistent sortable tables, plan_id auto-populate, mobile readiness (plot-fbe17883)","status":"open","type":"epic","priority":2,"createdAt":"2026-06-16T03:11:27.813Z","updatedAt":"2026-06-16T03:12:35.503Z","description":"Plot plot-fbe17883 'UI Nits'. Goal: eliminate a batch of warren UI papercuts so tables and run-launch flows behave consistently and reduce manual operator steps. Constraint: single shared sortable-table primitive over per-page sort logic. Success criteria: (1) consistent sort affordances across pages; (2) plan_id auto-populated after a planner finishes, mirroring plot_id prefill; (3) bespoke sort logic (Runs SortHeader/toggleSort, Workspace handleSort/sortIndicator) removed in favor of one shared primitive; (4) no unintended horizontal scroll at mobile widths; (5) modals/popups dismissible on mobile.","labels":["ui","papercuts"],"plan_id":"pl-3346","blockedBy":["warren-2728","warren-c869","warren-3c6f","warren-ae9f","warren-4734","warren-6f43"]} +{"id":"warren-2728","title":"Add shared sortable-table primitive: useSortableTable hook + SortableHeader component (server-driven and client-side modes) with unit tests","status":"open","type":"feature","priority":2,"plan_step_index":0,"description":"\nStep 1 of plan pl-3346.\n\nParent seed: warren-832d — UI Nits: consistent sortable tables, plan_id auto-populate, mobile readiness (plot-fbe17883)\nPlan template: feature\nPlan approach: Land one shared sortable-table primitive (a useSortableTable hook plus a SortableHeader component under src/ui/src/components/ui/) that owns sort key/dir state, the click-to-cycle decision, and a single consistent chevron affordance, with…\n\nRun `sd plan show pl-3346` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n","createdAt":"2026-06-16T03:12:35.503Z","updatedAt":"2026-06-16T03:12:35.503Z","labels":["ui","tables"],"plan_id":"pl-3346","blocks":["warren-c869","warren-3c6f","warren-832d"]} +{"id":"warren-c869","title":"Migrate Runs.tsx to the shared sortable-table primitive (server-driven mode): remove bespoke SortHeader/toggleSort, keep sort/dir API wiring and the 3-state cycle behavior","status":"open","type":"task","priority":2,"plan_step_index":1,"description":"\nStep 2 of plan pl-3346.\n\nParent seed: warren-832d — UI Nits: consistent sortable tables, plan_id auto-populate, mobile readiness (plot-fbe17883)\nPlan template: feature\nPlan approach: Land one shared sortable-table primitive (a useSortableTable hook plus a SortableHeader component under src/ui/src/components/ui/) that owns sort key/dir state, the click-to-cycle decision, and a single consistent chevron affordance, with…\n\nRun `sd plan show pl-3346` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n","createdAt":"2026-06-16T03:12:35.503Z","updatedAt":"2026-06-16T03:12:35.503Z","labels":["ui","tables"],"requires_plan":true,"blockedBy":["warren-2728"],"blocks":["warren-832d"]} +{"id":"warren-3c6f","title":"Migrate Workspace.tsx (and any other sortable tables) to the shared primitive: remove handleSort/sortIndicator text-arrow logic, standardize common columns (date/started, status) on the shared chevron affordance","status":"open","type":"task","priority":2,"plan_step_index":2,"description":"\nStep 3 of plan pl-3346.\n\nParent seed: warren-832d — UI Nits: consistent sortable tables, plan_id auto-populate, mobile readiness (plot-fbe17883)\nPlan template: feature\nPlan approach: Land one shared sortable-table primitive (a useSortableTable hook plus a SortableHeader component under src/ui/src/components/ui/) that owns sort key/dir state, the click-to-cycle decision, and a single consistent chevron affordance, with…\n\nRun `sd plan show pl-3346` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n","createdAt":"2026-06-16T03:12:35.503Z","updatedAt":"2026-06-16T03:12:35.503Z","labels":["ui","tables"],"requires_plan":true,"blockedBy":["warren-2728"],"blocks":["warren-832d"]} +{"id":"warren-ae9f","title":"Auto-populate plan_id after a planner finishes: read the planner run's emitted plan id (Plot sd_plan attachment) and prefill planId in dispatch-plan-dialog.tsx + NewPlanRun.tsx, mirroring NewRun's location.state plot_id prefill","status":"open","type":"feature","priority":2,"plan_step_index":3,"description":"\nStep 4 of plan pl-3346.\n\nParent seed: warren-832d — UI Nits: consistent sortable tables, plan_id auto-populate, mobile readiness (plot-fbe17883)\nPlan template: feature\nPlan approach: Land one shared sortable-table primitive (a useSortableTable hook plus a SortableHeader component under src/ui/src/components/ui/) that owns sort key/dir state, the click-to-cycle decision, and a single consistent chevron affordance, with…\n\nRun `sd plan show pl-3346` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n","createdAt":"2026-06-16T03:12:35.503Z","updatedAt":"2026-06-16T03:12:35.503Z","labels":["ui","plan-runs"],"plan_id":"pl-3346","blocks":["warren-832d"]} +{"id":"warren-4734","title":"Mobile readiness: audit every page at common mobile viewport widths and eliminate unintended horizontal scroll (flex min-w-0, table overflow wrappers, fixed-width layouts)","status":"open","type":"feature","priority":2,"plan_step_index":4,"description":"\nStep 5 of plan pl-3346.\n\nParent seed: warren-832d — UI Nits: consistent sortable tables, plan_id auto-populate, mobile readiness (plot-fbe17883)\nPlan template: feature\nPlan approach: Land one shared sortable-table primitive (a useSortableTable hook plus a SortableHeader component under src/ui/src/components/ui/) that owns sort key/dir state, the click-to-cycle decision, and a single consistent chevron affordance, with…\n\nRun `sd plan show pl-3346` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n","createdAt":"2026-06-16T03:12:35.503Z","updatedAt":"2026-06-16T03:12:35.503Z","labels":["ui","mobile"],"plan_id":"pl-3346","blocks":["warren-832d"]} +{"id":"warren-6f43","title":"Mobile dismissibility: audit all modals/popups (dialog primitive, mobile nav drawer) so each is dismissible on touch — no state requiring refresh or re-navigation to exit","status":"open","type":"feature","priority":2,"plan_step_index":5,"description":"\nStep 6 of plan pl-3346.\n\nParent seed: warren-832d — UI Nits: consistent sortable tables, plan_id auto-populate, mobile readiness (plot-fbe17883)\nPlan template: feature\nPlan approach: Land one shared sortable-table primitive (a useSortableTable hook plus a SortableHeader component under src/ui/src/components/ui/) that owns sort key/dir state, the click-to-cycle decision, and a single consistent chevron affordance, with…\n\nRun `sd plan show pl-3346` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n","createdAt":"2026-06-16T03:12:35.503Z","updatedAt":"2026-06-16T03:12:35.503Z","labels":["ui","mobile"],"plan_id":"pl-3346","blocks":["warren-832d"]} diff --git a/.seeds/plans.jsonl b/.seeds/plans.jsonl index 083d6302..7b554e08 100644 --- a/.seeds/plans.jsonl +++ b/.seeds/plans.jsonl @@ -65,6 +65,7 @@ {"id":"pl-da54","seed":"warren-d0ed","template":"feature","status":"done","revision":1,"sections":{"context":"Phase 2 of audit population (decision mx-1e041c). The three constitution auditors already run as cron triggers in .warren/triggers.yaml (gatewatch + ratchetwatch mechanical, tastewatch report-only) and file findings as seeds, but nothing synthesizes across them: there is no standing place where findings accumulate, get triaged, and turn into plans. warren-d0ed closes that gap with a Leveret warden — a single long-lived `mode:\"conversation\"` run bound to a dedicated meta-Plot that ingests findings over the EXISTING POST /conversations/:id/messages steering channel (already 202 today, src/server/handlers/conversations.ts postConversationMessageHandler), synthesizes a weekly digest, and proposes plans through the proven send-off->planner chain (sendOffConversationHandler -> conversation-merge-poller -> conversation-merge-dispatch). This is a deliberately THIN first slice: one standing conversation + weekly digest + plan proposals. Auditor-autonomy-promotion recommendations (from tastewatch's precision table) are explicitly deferred to a tracked follow-up. The whole slice is GATED on the Leveret repair arc (warren-87fe / pl-afed + the bridge warren-ce65 in pl-d2d9): live agent-authored intent (warren-ce65 bridge + warren-df1b plot ACL widening) and a default-on send-off->planner loop (warren-157a merge-poller default) must be working before warden findings can produce real plans. Because those repair-arc seeds are owned by other plans, this plan does not re-create or mutate them; it encodes the gate as a precondition in step 1 and in the acceptance criteria.","approach":"Reuse every existing primitive; add no new endpoint or dispatch path. (1) Bootstrap ONE standing meta-Plot and ONE standing Leveret conversation bound to it, created idempotently and resolvable by a stable, well-known identifier (a warren-config knob / well-known title) so other surfaces can find 'the warden conversation' deterministically. (2) Teach the three existing auditor cron procedures to deliver each finding to that conversation over POST /conversations/:id/messages (the 202 steering channel) — a prompt/agent-definition change, not a new code path. (3) Add a warden-digest cron trigger (reusing the existing kind:cron mechanism) that posts a 'synthesize the weekly digest' message to the standing conversation, so Leveret triages the week's accumulated findings and proposes at least one plan via the existing send-off->planner chain. (4) Prove the full loop with a deterministic acceptance scenario. (5) Close out the thin slice: file the deferred autonomy-promotion follow-up, document the warden boundary, and verify warren-d0ed's thin-slice acceptance. Chosen over a bespoke warden endpoint/daemon because the intent's constraints pin reuse of POST /conversations/:id/messages and forbid any new ingestion or dispatch primitive; the standing-conversation model also gives the digest a persistent transcript to triage instead of a per-finding fan-out.","alternatives":[{"name":"Per-finding conversation (one Leveret conversation per auditor finding)","rejected_because":"The intent explicitly requires a single STANDING conversation bound to a long-lived meta-Plot; per-finding conversations lose the cross-finding synthesis that makes a weekly digest possible and multiply lifetime/cost overhead."},{"name":"New warden ingestion endpoint or dispatch primitive that auditors call","rejected_because":"Constraint + non-goal forbid any new endpoint/dispatch/ingestion path — the warden must ride the existing POST /conversations/:id/messages 202 steering channel."},{"name":"Ship the warden now, ahead of the repair arc, and stub live intent","rejected_because":"The warden's value is proposing real plans through send-off->planner, which needs agent-authored live intent (warren-ce65 bridge + warren-df1b ACL) and a default-on merge poller (warren-157a). Stubbing those would ship a warden that cannot actually produce a plan."},{"name":"Include auditor-autonomy-promotion recommendations in this slice","rejected_because":"Non-goal: deferred to a tracked follow-up to keep the first slice thin (single standing conversation + weekly digest + plan proposals)."}],"steps":[{"title":"Bootstrap the standing warden: one long-lived meta-Plot + one standing mode:\"conversation\" Leveret run bound to it, created idempotently and resolvable by a stable well-known identifier (warren-config knob / well-known title) so auditors and the digest cron can find 'the warden conversation' deterministically. GATE: do not start until the Leveret repair arc has landed (live agent intent via warren-ce65 bridge + warren-df1b plot ACL, and default-on send-off->planner via warren-157a); verify the bridge + ACL + merge-poller default are in place as a precondition. Reuse resolveConversationPlot/createConversationHandler (src/server/handlers/conversations.ts) — no new creation primitive.","type":"feature","blocks":[2,3,4],"labels":["leveret","audit","warden"]},{"title":"Deliver auditor findings into the standing warden conversation over the EXISTING POST /conversations/:id/messages channel: update the gatewatch / ratchetwatch / tastewatch cron procedures (.warren/triggers.yaml prompts + their .canopy agent entries) so each finding is posted to the warden conversation (resolved via step 1's well-known id) as a 202 steering message. No new endpoint, ingestion, or dispatch primitive.","type":"feature","blocks":[4],"labels":["leveret","audit","warden"]},{"title":"Weekly digest trigger: add a warden-digest entry to .warren/triggers.yaml (reusing kind:cron) that posts a 'synthesize this week's audit digest and propose plans' message to the standing warden conversation via POST /conversations/:id/messages, so Leveret triages the accumulated findings transcript and drives the send-off->planner chain. Reuse re-wake (POST /conversations/:id/re-wake) if the anchoring run has idled. No new dispatch primitive.","type":"feature","blocks":[4],"labels":["leveret","audit","warden"]},{"title":"Acceptance scenario: a deterministic, idempotent scripts/acceptance/scenarios/ scenario (mirroring 33-leveret-conversation-loop.ts) that seeds the standing warden conversation, posts real-shaped auditor findings over POST /conversations/:id/messages, fires the digest message, and asserts Leveret synthesizes a digest AND proposes at least one plan through the existing send-off->planner chain (no new dispatch path). Self-cleans after itself.","type":"feature","blocks":[5],"labels":["leveret","audit","warden"]},{"title":"Close out the thin slice: file a tracked follow-up seed for auditor-autonomy-promotion recommendations from tastewatch's precision table (deferred per non-goal), document the warden boundary in LEVERET.md / docs/CONSTITUTION.md (standing conversation + meta-Plot + digest cadence, ingestion only via POST /conversations/:id/messages), run bun run check:all, and verify warren-d0ed's thin-slice acceptance is met so it closes.","type":"feature","blocks":[],"labels":["leveret","audit","warden"]}],"risks":["Repair-arc dependency not yet landed: warren-ce65 (bridge), warren-df1b (plot ACL), and warren-157a (merge-poller default) are still open. If the warden plan is dispatched before they merge, send-off cannot produce agent-authored intent or auto-dispatch a planner. Step 1's gate and acceptance criterion 1 exist to catch this; the plan is sequenced after the arc.","Standing-conversation lifetime: A long-lived mode:\"conversation\" anchoring run can idle-finalize (conversation.idleTimeoutMs, warren-005d); the digest cron and auditor posts must re-wake (POST /conversations/:id/re-wake) rather than assume a hot run, or findings posted while idle could be lost before replay.","Auditor procedure drift / Article IX review: Editing auditor prompts and .warren/triggers.yaml touches constitution-protected files (docs/CONSTITUTION.md Article IX) — changes need human-merge review and must not silently alter auditor autonomy.","Well-known id resolution fragility: Resolving 'the warden conversation' by title/config could break if duplicated; the bootstrap must be idempotent and assert a single canonical standing conversation per project."],"acceptance":["A single standing Leveret conversation bound to a dedicated long-lived meta-Plot exists, is created idempotently, and is resolvable by a stable well-known identifier.","All three auditors (gatewatch/ratchetwatch/tastewatch) deliver findings to the standing conversation via POST /conversations/:id/messages (202) with no new endpoint, ingestion, or dispatch primitive added.","The warden synthesizes a weekly digest from accumulated findings and proposes at least one plan through the existing send-off->planner chain, proven by a deterministic acceptance scenario.","An auditor-autonomy-promotion follow-up seed is filed and referenced; the warden boundary is documented; bun run check:all passes; warren-d0ed closes as the thin slice."]},"children":["warren-4372","warren-7f62","warren-4240","warren-6022","warren-05ef"],"createdAt":"2026-06-13T20:54:29.453Z","updatedAt":"2026-06-13T23:55:15.358Z","name":"Leveret warden — standing audit-triage conversation (thin slice)"} {"id":"pl-a141","seed":"warren-763e","template":"feature","status":"done","revision":2,"sections":{"steps":[]},"children":["warren-5f07","warren-1cae","warren-598f","warren-16f8","warren-157a","warren-a63d","warren-8dee"],"createdAt":"2026-06-13T20:39:49.468Z","updatedAt":"2026-06-14T01:08:39.526Z","name":"Polish pass","adoptedChildren":["warren-5f07","warren-1cae","warren-598f","warren-16f8","warren-157a","warren-a63d","warren-8dee"]} {"id":"pl-0008","seed":"warren-dde5","template":"feature","status":"done","revision":2,"sections":{"context":"Warren ships two top-level operator surfaces that are facets of the same object: Leveret (conversations list + /leveret/:id split-view) and Plots (list + /plots/:id three-panel detail + /plots/:id/summary). The data model already makes the Plot the durable spine and the conversation an ephemeral child of it: a conversation is bound to exactly one Plot, send-off CLOSES the conversation, and re-planning spawns a NEW conversation on the SAME Plot. On top of the redundancy, the Plot surface still renders a fully RETIRED intent-shaping path next to the live one: warren-d622 removed mode=interactive (RUN_MODES is now [batch, conversation]) and the POST /brainstorm route, yet the Plots page still shows a 'Start brainstorming' button (plotsApi.startBrainstorm -> dead POST /brainstorm) and PlotDetail still renders InteractivePanel (runsApi.createInteractive with mode:interactive, which the schema rejects) plus an orphaned Formalize (POST /plots/:id/formalize summarizing a brainstorm conversation nothing can create). Operators cannot walk one coherent flow; they bounce between /leveret/:id and /plots/:id and trip over dead buttons. This collapses both surfaces into a single cross-project Workspace and sweeps the dead brainstorm/interactive/formalize code, which also gives R-20 (colony / multi-repo) a natural top-level home to grow into.","approach":"Make the Plot the spine and the conversation a facet of it. Introduce one 'Workspace' top-level section: a single cross-project list (one row per Plot, with an active-conversation indicator) and one tabbed detail page (/workspace/:id) that walks the operator's own four-step mental model as tabs — Shape (brainstorm with Leveret: chat + dynamic intent editor + send-off), Plan (convert into plan: planner run + link out to the sd plan + sign-off gate + dispatch), Run (dispatch plan-run: children + PR-merge status + Plot auto-done), and Activity (event log + substrate + summary + past conversations). Reuse the existing sub-component trees verbatim (conversation-detail/* for Shape, plot-detail/* minus InteractivePanel for the rest, dispatch-plan-dialog for Plan, PlanRunDetail content for Run) so this is a re-composition, not a rewrite. Sweep the retired surface in the same arc: delete Formalize end to end, delete the dead UI panels, and remove the dead client methods. Old routes redirect into Workspace so no bookmark/link breaks. Sequenced server-cleanup-first so the UI client sweep compiles against an already-clean server, then list -> shell -> four tabs -> nav/routing collapse -> acceptance -> docs. Chosen over a Plot-centric merge that keeps 'Leveret' as a noun (rejected: conversation is ephemeral, a poor spine) and over a conversation-centric workspace (rejected: a Plot has N conversations over its life).","alternatives":[{"name":"Conversation-centric workspace (lead with the Leveret conversation, embed plot state)","rejected_because":"Send-off closes the conversation and re-plan spawns a new one on the same Plot, so a Plot has 1..N conversations over its life — the conversation is a poor durable spine."},{"name":"Keep two pages, only delete the dead brainstorm/formalize code","rejected_because":"Leaves the core complaint (two intertwined surfaces, no single walkable flow) unsolved."},{"name":"Rewire Formalize onto the Leveret transcript instead of deleting it","rejected_because":"Operator decision — Leveret already shapes intent live via propose_intent, so a separate summarize-then-apply step is redundant."},{"name":"Single scrollable detail page instead of tabs","rejected_because":"Operator decision — after intent is shaped, talking to Leveret does nothing, so phase tabs that go quiet/read-only model the lifecycle better than one long scroll."}],"steps":[{"title":"Server sweep — remove the orphaned Formalize endpoint. Delete POST /plots/:id/formalize from ROUTE_TABLE (src/server/handlers/index.ts), the formalizePlotHandler + brainstorm-summarize logic in src/server/handlers/plots/workbench.ts, and src/server/handlers/plots.workbench.formalize.test.ts. Regenerate docs/http-api.md (bun run gen:docs) and docs/openapi.yaml (bun run gen:openapi). Verify no remaining server caller references formalize.","type":"task","priority":2,"blocks":[3],"labels":["workspace","server","cleanup"]},{"title":"Server sweep — remove residual mode=interactive / brainstorm dispatch remnants left after warren-d622. Audit src/server/handlers/runs/ (parseRunMode + createRunHandler) and src/runs/spawn/dispatch.ts for any mode:'interactive' / interactiveAgent handling and the 'brainstorm' interactive built-in remnants; remove dead branches while PRESERVING the 'planner' interactive agent and mode:'conversation'. Remove the vestigial interactiveAgents.brainstormRuntime knob from src/warren-config/schema.ts only if it is unreferenced after the sweep. Update or trim affected tests.","type":"task","priority":2,"blocks":[3],"labels":["workspace","server","cleanup"]},{"title":"UI API-client sweep — remove the dead client methods and types: plotsApi.startBrainstorm (+ StartBrainstormInput/Response), runsApi.createInteractive, and plotsApi.formalize (+ formalize request/response types) from src/ui/src/api/client.ts and src/ui/src/api/types.ts. Fix every TypeScript reference so bun run typecheck and the src/ui tsc both pass.","type":"task","priority":2,"blocks":[4],"labels":["workspace","ui","cleanup"]},{"title":"Delete the dead UI panels. Remove src/ui/src/pages/plot-detail/interactive-panel.tsx and its usage in PlotDetail.tsx (InteractivePanel — Start brainstorming / Run planner / Formalize, all built on retired mode=interactive). Remove StartBrainstormDialog + the 'Start brainstorming' button from src/ui/src/pages/Plots.tsx. KEEP all still-live plot-detail/ sub-components (intent-panel, substrate-panel, activity-feed, header-controls, run-plan, batch-dispatch). Update affected component tests.","type":"task","priority":2,"blocks":[5],"labels":["workspace","ui","cleanup"]},{"title":"Build the Workspace list page. Add src/ui/src/pages/Workspace.tsx at a new /workspace route: a single cross-project list with one row per Plot (the durable spine), merging today's Plots list and Leveret conversations list. Columns: name, project, status/phase, intent preview, active-conversation indicator, needs-you reasons, last activity. Reuse the Plots server ?status/?filter chip contract. Actions: New Plot (existing dialog) and Start conversation (existing NewConversationButton). Backed by plotsApi.list joined with conversationsApi.list.","type":"task","priority":2,"blocks":[6],"labels":["workspace","ui"]},{"title":"Build the Workspace detail shell. Add src/ui/src/pages/WorkspaceDetail.tsx at /workspace/:id (keyed by plotId): a persistent header (PlotNameEditor, StatusTransitionControl, PlotSyncButton, project link, summary link) plus tab navigation Shape / Plan / Run / Activity with tab state in a ?tab= query param. Wire empty tab-panel child components to be filled by steps 7-10. Load the plot via plotsApi.get and reuse the existing 404/refresh-projects CTA.","type":"task","priority":2,"blocks":[7,8,9,10],"labels":["workspace","ui"]},{"title":"Implement the Shape tab. Port the live Leveret conversation surface into Shape: the streamed Chat (sendMessage override -> POST /conversations/:id/messages) on the left and the dynamic intent editor (fieldsFromIntent / patchFromFields) on the right, plus RewakeButton and the SendOff action — reusing the conversation-detail/ sub-components. Resolve the Plot's active conversation via conversationsApi.list({plot}); when none is active, render a Start-conversation affordance. After send-off the tab goes read-only/quiet.","type":"task","priority":2,"blocks":[11],"labels":["workspace","ui"]},{"title":"Implement the Plan tab (minimal). Once the merge-poller stamps conversation.plannerRunId, show the planner run status and a link out to the generated sd plan, then an operator Sign-off gate that enables the existing DispatchPlanButton (conversation-detail/dispatch-plan-dialog.tsx). No new inline plan renderer — link to the plan and dispatch over the existing /plan-runs path.","type":"task","priority":2,"blocks":[11],"labels":["workspace","ui"]},{"title":"Implement the Run tab. Embed plan-run execution for the dispatched plan: children list + per-child PR-merge status + terminal state, reusing the PlanRunDetail content, and surface the Plot's auto-done transition when the final child merges (SPEC §11.P.Plot). Resolve the plan-run from the dispatched plot/plan.","type":"task","priority":2,"blocks":[11],"labels":["workspace","ui"]},{"title":"Implement the Activity tab. Move the Plot event-log ActivityFeed (plot-detail/activity-feed.tsx) and the SubstratePanel (attachments) into the Activity tab, plus a link to the read-only Plot summary. Past/closed conversations for the Plot surface here as history entries so the full lifecycle is visible from one place.","type":"task","priority":2,"blocks":[11],"labels":["workspace","ui"]},{"title":"Collapse nav + routing. Replace the 'Leveret' and 'Plots' sidebar entries in src/ui/src/components/Layout.tsx with a single 'Workspace' entry and move the needs-you badge onto it. Register /workspace + /workspace/:id; add redirects /plots -> /workspace, /plots/:id -> /workspace/:id, /leveret -> /workspace, and /leveret/:id -> resolve conversation.plotId then redirect to /workspace/:plotId?tab=shape. Delete the now-unused page shells Leveret.tsx, Plots.tsx, ConversationDetail.tsx, PlotDetail.tsx (keeping their reused sub-component trees). Update the router.","type":"task","priority":2,"blocks":[12],"labels":["workspace","ui"]},{"title":"Update acceptance + UI tests for the Workspace flow. Adapt scenario 33 (scripts/acceptance/scenarios/33-leveret-conversation-loop.ts) and any UI tests referencing the old /leveret + /plots routes/pages to the new Workspace surface; keep the scenario deterministic, idempotent, and self-cleaning. Assert the full walk: list -> Shape (conversation + intent) -> send-off -> Plan (planner + dispatch) -> Run.","type":"task","priority":2,"blocks":[13],"labels":["workspace","acceptance"]},{"title":"Docs, gates, and bundle re-baseline. Update README + SPEC §11.O/§11.P UI references + LEVERET.md to describe the single Workspace surface and the removed brainstorm/formalize path; regen docs/http-api.md + docs/openapi.yaml; re-baseline bundle budgets (bun run check:bundle-size --update); get bun run check:all green. File a follow-up seed for the R-20 colony/multi-repo tier layered on top of Workspace.","type":"task","priority":2,"blocks":[],"labels":["workspace","docs"]}],"risks":["Route-redirect fragility: /leveret/:id is keyed by conversation id, not plot id, so the redirect must resolve conversation.plotId before sending the user to /workspace/:plotId — a naive id passthrough 404s. Covered in step 11.","Reused sub-component coupling: deleting the four page shells (step 11) while steps 7-10 still import their sub-trees (conversation-detail/*, plot-detail/*) risks breaking imports — keep the sub-component directories, only delete the page-level shells.","Server sweep over-reach: the 'planner' agent and mode:'conversation' must survive the interactive/brainstorm removal in step 2; an over-broad delete would break send-off -> planner. Preserve planner explicitly.","Bundle-size ratchet: a re-composition that changes chunking can trip check:bundle-size; re-baseline with the canonical --update (step 13), never hand-edit the budget JSON.","Plot 404-after-commit: the existing refresh-projects CTA must be carried into WorkspaceDetail (step 6) or freshly-committed Plots look broken."],"acceptance":["A single 'Workspace' top-level nav entry replaces both 'Leveret' and 'Plots'; the needs-you badge renders on it; /plots, /plots/:id, /leveret, and /leveret/:id all redirect into the Workspace surface without 404s.","/workspace shows one cross-project list keyed one-row-per-Plot with an active-conversation indicator; New Plot and Start conversation both work from it.","/workspace/:id presents Shape / Plan / Run / Activity tabs that walk the full flow: shape intent with Leveret, send off, review + sign off + dispatch the plan, watch the plan-run execute, and read activity/summary — all without leaving the page.","The retired surface is gone end to end: POST /plots/:id/formalize, InteractivePanel, StartBrainstormDialog, plotsApi.startBrainstorm, runsApi.createInteractive, and plotsApi.formalize no longer exist; no dead button reaches a removed endpoint.","bun run check:all passes (lint, typecheck, check:agents, dups, deps, size, debt, bundle-size, gen:docs:check, gen:openapi:check, coverage, ci-parity) and the updated acceptance scenario asserts the end-to-end Workspace walk.","A follow-up seed is filed for the R-20 colony / multi-repo tier on top of Workspace."]},"children":["warren-cef0","warren-d861","warren-b265","warren-af60","warren-dc54","warren-6e7d","warren-3de4","warren-e33f","warren-d17f","warren-ef97","warren-9cad","warren-1198","warren-3f94","warren-8363"],"createdAt":"2026-06-13T23:44:28.363Z","updatedAt":"2026-06-14T03:37:41.815Z","name":"Collapse Leveret + Plot into one tabbed Workspace","adoptedChildren":["warren-8363"]} -{"id":"pl-2e59","seed":"warren-faab","template":"feature","status":"done","revision":1,"sections":{"context":"Plot plot-b11c5999 (\"Workspaces Testing\") finalized a low-risk cleanup batch capped by a 0.9.1 patch bump. Its goal names four deliverable areas: (1) the full logging/traceability hardening set, (2) workspace chat surface fixes — out-of-order turns, duplicated assistant messages, and unbounded page growth, (3) removal of the leaked .pi/sessions agent transcript from PR #340, and (4) the version bump. Area (1) is already an approved, decomposed plan: pl-f700 (parent warren-3679) with six child seeds c686 → fc6e → af76 → 26c2 → 9f06 → b2dd. A seed can only belong to one plan (plan_id is single-valued), so this plan does NOT re-adopt those six — that would re-parent them out of pl-f700 and gut the approved logging plan. Instead pl-f700 stands as the logging sub-sequence under the same Plot, and this plan covers only the net-new tail. The chat ordering/dedup defect lives in src/ui/src/components/chat-messages.ts: buildChatMessages concatenates two seq-sorted groups (transcript first, then stream) instead of merging on a single unified ordering key, so turns render transcript-first-then-stream rather than chronologically, and the (kind, content) dedupe is loose enough that a streamed assistant turn and its persisted copy can both render. The unbounded-growth defect is in src/ui/src/pages/conversation-detail/conversation-surface.tsx: the conversation Card uses min-h-[60vh] with no upper bound, so Chat.tsx's existing internal `flex-1 min-h-0 overflow-y-auto` (Chat.tsx:152/157) never engages and the /workspaces page grows with the transcript. warren-4c8d (gatewatch) documents the exact leaked file from PR #340 and is currently unplanned, so it is adopted here. The version lives in two synced places (package.json + src/index.ts VERSION) plus a CHANGELOG.md section; the release workflow fails on drift.","approach":"Walk the net-new tail serially — one seed per PR, each gated on the prior PR merging — mirroring the polish-pass convention and the Plot's stated constraint. Order: chat ordering/dedup fix → chat unbounded-growth fix → leaked-transcript removal → 0.9.1 version bump (last, capping the batch). The whole Plot batch is gated behind pl-f700: the six logging PRs land first, then this tail, so the 0.9.1 bump is the final PR after every logging + chat + transcript PR merges (the version-bump seed records that ordering since a hard cross-plan block edge to warren-b2dd would mutate a seed this run did not create). Chat fixes are split into two seeds because they touch independent files (the pure merge logic vs. the page layout) and are independently reviewable: the ordering/dedup fix is pure, unit-testable logic with a regression test added to chat-messages.test.ts; the growth fix is a layout-only change that bounds the conversation card's ancestor height to engage the existing overflow-y-auto rather than introducing a new scroll container. Transcript removal is adopted from the existing warren-4c8d rather than re-authored. All seeds pass `bun run check:all` before merge.","alternatives":[{"name":"Re-adopt the six pl-f700 logging seeds into this umbrella plan as a literal contiguous step sub-sequence","rejected_because":"A seed's plan_id is single-valued; adopting c686/fc6e/af76/26c2/9f06/b2dd here would re-parent them off the approved pl-f700, gutting that plan and its review state. The Plot already owns the logging sub-sequence via pl-f700; this plan references it for ordering instead of destroying it."},{"name":"Fix chat ordering and dedup in one seed with the unbounded-growth fix","rejected_because":"They touch different files (pure merge logic in chat-messages.ts vs. layout height in conversation-surface.tsx) and have different review surfaces and test strategies. Splitting keeps each PR small, single-concern, and serially reviewable per the batch convention."},{"name":"Introduce a new fixed-height scroll container for the workspace chat","rejected_because":"Chat.tsx already has a working internal overflow-y-auto; the page grows only because no ancestor bounds its height. Bounding the existing ancestor is the minimal fix and avoids a second competing scroll region."}],"steps":[{"title":"Fix workspace chat ordering + dedup in src/ui/src/components/chat-messages.ts: merge transcript + stream bubbles on a single unified ordering key (chronological across the transcript/stream boundary) instead of concatenating the two seq-sorted groups, and tighten buildChatMessages dedupe so a streamed assistant turn and its persisted transcript copy collapse to one bubble; add regression tests to chat-messages.test.ts","type":"bug","priority":1,"blocks":[2]},{"title":"Fix unbounded /workspaces growth: bound the conversation card's ancestor height in src/ui/src/pages/conversation-detail/conversation-surface.tsx (min-h-[60vh] → a bounded height) so Chat.tsx's existing internal flex-1 min-h-0 overflow-y-auto engages and the transcript scrolls internally instead of growing the page; no new scroll container","type":"bug","priority":2,"blocks":[3]},{"existing_seed":"warren-4c8d","blocks":[4]},{"title":"Bump version to 0.9.1: set package.json \"version\" and src/index.ts VERSION to 0.9.1 in sync (release workflow fails on drift) and add a matching CHANGELOG.md 0.9.1 section summarizing the logging hardening (pl-f700), workspace chat fixes, and leaked-transcript removal; land last, after every pl-f700 logging PR plus the chat + transcript PRs merge","type":"task","priority":1,"blocks":[]}],"risks":["The unified ordering key must be stable when transcript rows and stream events share a seq space or use disjoint seq ranges — verify the merge against a never-started anchoring run (transcript only) and an active run (both present) so neither regresses; the existing chat-messages.test.ts cases must stay green.","Tightening dedupe must not collapse legitimately repeated tool/thinking rows (they intentionally key on event id, not content) — keep that behavior while making user/agent turns collapse across transcript+stream.","Bounding the conversation card height must not clip the input row or the plot-intent column on small viewports; verify both the conversation pane and the sibling intent pane in the lg:grid-cols-2 layout.","Version-bump drift: package.json and src/index.ts must match exactly or the release workflow fails; the CHANGELOG section header must match the X.Y.Z the release job greps for.","Serial gating across two plans (pl-f700 + this one) is advisory, not a hard block edge, since wiring warren-b2dd → step 1 would mutate a seed not created in this run; the version-bump seed and umbrella seed document the ordering so dispatch respects it."],"acceptance":["On the workspace conversation surface, user and agent turns render in correct chronological order across the transcript/stream boundary (no transcript-first-then-stream reordering).","No assistant message is duplicated when a streamed turn and its persisted transcript copy both exist; the buildChatMessages dedupe is covered by a new regression test in chat-messages.test.ts.","The workspace conversation transcript scrolls internally and the /workspaces page no longer grows unbounded, using Chat.tsx's existing overflow-y-auto via a bounded ancestor height (no new scroll container).","The leaked .pi/sessions agent transcript from PR #340 is removed from the repo (warren-4c8d).","VERSION in src/index.ts and \"version\" in package.json are both 0.9.1 and in sync, with a matching CHANGELOG.md 0.9.1 section.","Every seed in this plan passes `bun run check:all` (lint, typecheck, size, debt, dups, coverage, etc.) before merge."]},"children":["warren-f4b8","warren-5755","warren-4c8d","warren-fade"],"createdAt":"2026-06-14T05:54:42.639Z","updatedAt":"2026-06-16T03:14:06.231Z","name":"Cleanup batch → 0.9.1 (net-new tail of pl-f700 logging)","adoptedChildren":["warren-4c8d"]} +{"id":"pl-2e59","seed":"warren-faab","template":"feature","status":"done","revision":1,"sections":{"context":"Plot plot-b11c5999 (\"Workspaces Testing\") finalized a low-risk cleanup batch capped by a 0.9.1 patch bump. Its goal names four deliverable areas: (1) the full logging/traceability hardening set, (2) workspace chat surface fixes — out-of-order turns, duplicated assistant messages, and unbounded page growth, (3) removal of the leaked .pi/sessions agent transcript from PR #340, and (4) the version bump. Area (1) is already an approved, decomposed plan: pl-f700 (parent warren-3679) with six child seeds c686 → fc6e → af76 → 26c2 → 9f06 → b2dd. A seed can only belong to one plan (plan_id is single-valued), so this plan does NOT re-adopt those six — that would re-parent them out of pl-f700 and gut the approved logging plan. Instead pl-f700 stands as the logging sub-sequence under the same Plot, and this plan covers only the net-new tail. The chat ordering/dedup defect lives in src/ui/src/components/chat-messages.ts: buildChatMessages concatenates two seq-sorted groups (transcript first, then stream) instead of merging on a single unified ordering key, so turns render transcript-first-then-stream rather than chronologically, and the (kind, content) dedupe is loose enough that a streamed assistant turn and its persisted copy can both render. The unbounded-growth defect is in src/ui/src/pages/conversation-detail/conversation-surface.tsx: the conversation Card uses min-h-[60vh] with no upper bound, so Chat.tsx's existing internal `flex-1 min-h-0 overflow-y-auto` (Chat.tsx:152/157) never engages and the /workspaces page grows with the transcript. warren-4c8d (gatewatch) documents the exact leaked file from PR #340 and is currently unplanned, so it is adopted here. The version lives in two synced places (package.json + src/index.ts VERSION) plus a CHANGELOG.md section; the release workflow fails on drift.","approach":"Walk the net-new tail serially — one seed per PR, each gated on the prior PR merging — mirroring the polish-pass convention and the Plot's stated constraint. Order: chat ordering/dedup fix → chat unbounded-growth fix → leaked-transcript removal → 0.9.1 version bump (last, capping the batch). The whole Plot batch is gated behind pl-f700: the six logging PRs land first, then this tail, so the 0.9.1 bump is the final PR after every logging + chat + transcript PR merges (the version-bump seed records that ordering since a hard cross-plan block edge to warren-b2dd would mutate a seed this run did not create). Chat fixes are split into two seeds because they touch independent files (the pure merge logic vs. the page layout) and are independently reviewable: the ordering/dedup fix is pure, unit-testable logic with a regression test added to chat-messages.test.ts; the growth fix is a layout-only change that bounds the conversation card's ancestor height to engage the existing overflow-y-auto rather than introducing a new scroll container. Transcript removal is adopted from the existing warren-4c8d rather than re-authored. All seeds pass `bun run check:all` before merge.","alternatives":[{"name":"Re-adopt the six pl-f700 logging seeds into this umbrella plan as a literal contiguous step sub-sequence","rejected_because":"A seed's plan_id is single-valued; adopting c686/fc6e/af76/26c2/9f06/b2dd here would re-parent them off the approved pl-f700, gutting that plan and its review state. The Plot already owns the logging sub-sequence via pl-f700; this plan references it for ordering instead of destroying it."},{"name":"Fix chat ordering and dedup in one seed with the unbounded-growth fix","rejected_because":"They touch different files (pure merge logic in chat-messages.ts vs. layout height in conversation-surface.tsx) and have different review surfaces and test strategies. Splitting keeps each PR small, single-concern, and serially reviewable per the batch convention."},{"name":"Introduce a new fixed-height scroll container for the workspace chat","rejected_because":"Chat.tsx already has a working internal overflow-y-auto; the page grows only because no ancestor bounds its height. Bounding the existing ancestor is the minimal fix and avoids a second competing scroll region."}],"steps":[{"title":"Fix workspace chat ordering + dedup in src/ui/src/components/chat-messages.ts: merge transcript + stream bubbles on a single unified ordering key (chronological across the transcript/stream boundary) instead of concatenating the two seq-sorted groups, and tighten buildChatMessages dedupe so a streamed assistant turn and its persisted transcript copy collapse to one bubble; add regression tests to chat-messages.test.ts","type":"bug","priority":1,"blocks":[2]},{"title":"Fix unbounded /workspaces growth: bound the conversation card's ancestor height in src/ui/src/pages/conversation-detail/conversation-surface.tsx (min-h-[60vh] → a bounded height) so Chat.tsx's existing internal flex-1 min-h-0 overflow-y-auto engages and the transcript scrolls internally instead of growing the page; no new scroll container","type":"bug","priority":2,"blocks":[3]},{"existing_seed":"warren-4c8d","blocks":[4]},{"title":"Bump version to 0.9.1: set package.json \"version\" and src/index.ts VERSION to 0.9.1 in sync (release workflow fails on drift) and add a matching CHANGELOG.md 0.9.1 section summarizing the logging hardening (pl-f700), workspace chat fixes, and leaked-transcript removal; land last, after every pl-f700 logging PR plus the chat + transcript PRs merge","type":"task","priority":1,"blocks":[]}],"risks":["The unified ordering key must be stable when transcript rows and stream events share a seq space or use disjoint seq ranges — verify the merge against a never-started anchoring run (transcript only) and an active run (both present) so neither regresses; the existing chat-messages.test.ts cases must stay green.","Tightening dedupe must not collapse legitimately repeated tool/thinking rows (they intentionally key on event id, not content) — keep that behavior while making user/agent turns collapse across transcript+stream.","Bounding the conversation card height must not clip the input row or the plot-intent column on small viewports; verify both the conversation pane and the sibling intent pane in the lg:grid-cols-2 layout.","Version-bump drift: package.json and src/index.ts must match exactly or the release workflow fails; the CHANGELOG section header must match the X.Y.Z the release job greps for.","Serial gating across two plans (pl-f700 + this one) is advisory, not a hard block edge, since wiring warren-b2dd → step 1 would mutate a seed not created in this run; the version-bump seed and umbrella seed document the ordering so dispatch respects it."],"acceptance":["On the workspace conversation surface, user and agent turns render in correct chronological order across the transcript/stream boundary (no transcript-first-then-stream reordering).","No assistant message is duplicated when a streamed turn and its persisted transcript copy both exist; the buildChatMessages dedupe is covered by a new regression test in chat-messages.test.ts.","The workspace conversation transcript scrolls internally and the /workspaces page no longer grows unbounded, using Chat.tsx's existing overflow-y-auto via a bounded ancestor height (no new scroll container).","The leaked .pi/sessions agent transcript from PR #340 is removed from the repo (warren-4c8d).","VERSION in src/index.ts and \"version\" in package.json are both 0.9.1 and in sync, with a matching CHANGELOG.md 0.9.1 section.","Every seed in this plan passes `bun run check:all` (lint, typecheck, size, debt, dups, coverage, etc.) before merge."]},"children":["warren-f4b8","warren-5755","warren-4c8d","warren-fade"],"createdAt":"2026-06-14T05:54:42.639Z","updatedAt":"2026-06-16T03:14:22.656Z","name":"Cleanup batch → 0.9.1 (net-new tail of pl-f700 logging)","adoptedChildren":["warren-4c8d"]} {"id":"pl-88bb","seed":"warren-60ee","template":"refactor","status":"approved","revision":1,"sections":{"context":"Constitution Article II ratchet tightening (2026-06-14 ratchetwatch patrol). src/runs/pr.ts is 659 lines — the grandfathered file furthest over the 500-line global limit (scripts/file-size-budgets.json: \"src/runs/pr.ts\": 659) and not covered by any open seed. Grandfather entries are silent debt; the one-decomposition-per-patrol slot retires this one so the budget entry can be removed (ratchet only goes down).","behavior_invariant":"Every symbol src/runs/pr.ts exports today keeps the same name, signature, and runtime behavior, importable from the same module path src/runs/pr.ts (re-export from pr.ts if a symbol physically moves to a sibling). Current importers — src/server/main/index.ts, src/server/handlers/plots/attachments.ts, src/plots/sync.ts, src/plots/pr-merger.ts, src/plan-runs/pr-merge.ts, src/runs/pr-annotate.ts, src/runs/index.ts, src/runs/reap/run.ts, src/runs/reap/pr-open.ts, src/runs/reap/types.ts, src/runs/pr-template.ts — compile and pass unchanged. The full src/runs/pr.test.ts suite stays green with the same test count.","approach":"Split along the existing seam: move the PR-merge / URL-parsing group (checkPullRequestMerged, parsePullRequestUrl, parsePullRequestRef, mergePullRequest, isRateLimited, plus their CheckPullRequestMergedInput/CheckPrMergedResult/MergePullRequestInput/MergePullRequestResult types and PR_URL_RE/PR_SHORT_RE regexes — roughly lines 403-659) into a new sibling module src/runs/pr-checks.ts (do NOT reuse the name pr-merge.ts; src/plan-runs/pr-merge.ts already exists). Re-export the moved public symbols from src/runs/pr.ts so the module path is unchanged for every importer. That cut removes ~250 lines, landing pr.ts near ~405 lines — comfortably under 500. Keep openPullRequest, buildPrContent, loadAutoOpenPrConfigFromEnv and friends in pr.ts.","steps":[{"title":"Create src/runs/pr-checks.ts and move the PR-merge/URL-parse group into it. Move checkPullRequestMerged, mergePullRequest, parsePullRequestUrl, parsePullRequestRef, isRateLimited and their associated exported types/regexes (CheckPullRequestMergedInput, CheckPrMergedResult, MergePullRequestInput, MergePullRequestResult, PR_URL_RE, PR_SHORT_RE) out of src/runs/pr.ts into the new src/runs/pr-checks.ts, carrying any private helpers they need (buildHeaders/readJson/readText/truncate may need to be shared — if so, keep one copy and import it, do NOT duplicate, or jscpd check:dups will flag it). Re-export every moved PUBLIC symbol from src/runs/pr.ts (export { ... } from \"./pr-checks.ts\") so the path src/runs/pr.ts still resolves all of them. Verify: `wc -l src/runs/pr.ts` shows < 500 AND `bun run typecheck` is clean AND `bun test src/runs/pr.test.ts` is green with the same test count as before the split.","labels":["ratchetwatch"]},{"title":"Remove the \"src/runs/pr.ts\": 659 entry from scripts/file-size-budgets.json (delete that one line). Do NOT add a budget entry for src/runs/pr-checks.ts — it must default-pass under the 500 threshold (confirm `wc -l src/runs/pr-checks.ts` < 500). Per Constitution Article VI, before declaring done run a repo-wide search for any reference to the old single-file assumption and the new path across ALL file types — `rg -n \"runs/pr\\\\.ts|runs/pr-checks\" --hidden -g '!node_modules'` plus an explicit sweep of Dockerfile, docker-compose.yml, .github/workflows/*.yml, src/supervisor/ spawn/config strings, and docs/ — and fix any stale reference (file moves have broken production here before; encode the check, do not assume it). Verify: `bun run check:size` exits 0 AND `bun run check:dups` exits 0 AND `bun run check:all` is fully green (every gate stays green, the raised-tightness budget still passes against current actuals).","labels":["ratchetwatch"],"blocks":[]}],"acceptance":["src/runs/pr.ts is < 500 lines and its budget entry is gone from scripts/file-size-budgets.json; src/runs/pr-checks.ts is < 500 lines with no budget entry.","All prior importers compile unchanged and `bun run typecheck` is clean — every public symbol is still importable from src/runs/pr.ts.","`bun test src/runs/pr.test.ts` passes with the same test count as before the split (behavior invariant preserved).","Article VI repo-wide old-path/new-path search (rg + Dockerfile/compose/workflow YAML/supervisor strings/docs) shows no stale or broken references.","`bun run check:all` is fully green — no gate regresses and no release step is added (Article III: hygiene batches into the next real release)."]},"children":["warren-db9a","warren-70d7"],"createdAt":"2026-06-14T10:46:18.235Z","updatedAt":"2026-06-14T10:46:18.235Z","name":"Decompose src/runs/pr.ts below the 500-line limit"} {"id":"pl-ef08","seed":"warren-fa85","template":"refactor","status":"done","revision":1,"sections":{"context":"Constitution Article II ratchet tightening (2026-06-15 ratchetwatch patrol). scripts/acceptance/scenarios/20-preview.ts is 658 lines — grandfathered at \"scripts/acceptance/scenarios/20-preview.ts\": 658 in scripts/file-size-budgets.json. src/runs/pr.ts (659, furthest over the 500-line limit) is already covered by open plan pl-88bb (warren-db9a/warren-70d7), so 20-preview.ts is the furthest-over grandfathered file NOT covered by an open seed and takes this patrol's single one-decomposition-per-patrol slot. Retiring it lets the budget entry be removed (ratchet only goes down).","behavior_invariant":"The acceptance scenario keeps the exact same `scenario` export (id 20, both variants A happy-path + B idle-TTL eviction) importable from the unchanged path scripts/acceptance/scenarios/20-preview.ts. scripts/acceptance/run.ts line `import { scenario as scenario20 } from \"./scenarios/20-preview.ts\";` stays byte-identical and resolves. runVariantA/runVariantB behavior is unchanged. Scenario 20 still passes (or skips with its documented non-Linux guard) exactly as before. No other scenario imports these helpers (20-preview-path.ts does NOT import from 20-preview.ts — verified), so the move is file-local.","approach":"Mirror the established precedent scripts/acceptance/scenarios/32-plot-workbench-loop.helpers.ts: extract the file-local test-helper group into a sibling `scripts/acceptance/scenarios/20-preview.helpers.ts` and import it back. Move the contiguous helper block (roughly lines 390-658): buildPreviewProjectFixture, commitInSource, runGit, ensureProject, waitForRunTerminal, waitForPreviewState, fetchEvents, proxyRequest, loginAndIssueCookie, parseSetCookie, sleep, plus their helper-local types BuildFixtureInput, BuiltPreviewFixture, ProxyRequestInput, ProxyResponse, LoginInput. The shared row types used by BOTH the scenario body and the helpers (ProjectRow used by ensureProject, EventRow used by fetchEvents) move to the helpers file and are re-imported into 20-preview.ts (same pattern as RunRow in 32's helpers) — do NOT duplicate a type in both files or jscpd check:dups will flag it. Keep `scenario`, runVariantA, runVariantB, and the file header comment in 20-preview.ts. Removing ~268 lines lands 20-preview.ts near ~390 lines; the new helpers file is ~270 lines — both comfortably under 500. Discovery is import-by-name in run.ts (no glob), so a `.helpers.ts` sibling is never auto-loaded as a scenario.","steps":[{"title":"Create scripts/acceptance/scenarios/20-preview.helpers.ts and move the file-local helper group into it. Move buildPreviewProjectFixture, commitInSource, runGit, ensureProject, waitForRunTerminal, waitForPreviewState, fetchEvents, proxyRequest, loginAndIssueCookie, parseSetCookie, sleep, plus their helper-local types (BuildFixtureInput, BuiltPreviewFixture, ProxyRequestInput, ProxyResponse, LoginInput) and the shared row types they need (ProjectRow, EventRow), out of scripts/acceptance/scenarios/20-preview.ts into the new sibling file, exporting each symbol the scenario body still references. Carry the imports those helpers need (node:crypto/node:fs/promises/node:os/node:path, ../lib/assert.ts, ../lib/http.ts WarrenHttp, ../lib/inproc.ts) into the helpers file; if a private utility is needed by both files keep ONE copy and import it (do NOT duplicate, or jscpd check:dups will flag it). Import the moved symbols back into 20-preview.ts. Keep the `scenario` export, runVariantA, runVariantB, and the header comment in 20-preview.ts unchanged. Mirror the existing precedent scripts/acceptance/scenarios/32-plot-workbench-loop.helpers.ts. Verify: `wc -l scripts/acceptance/scenarios/20-preview.ts` shows < 500 AND `wc -l scripts/acceptance/scenarios/20-preview.helpers.ts` shows < 500 AND `bun run typecheck` is clean (run.ts still resolves the unchanged `scenario as scenario20` import).","labels":["ratchetwatch"]},{"title":"Remove the \"scripts/acceptance/scenarios/20-preview.ts\": 658 entry from scripts/file-size-budgets.json (delete that one line). Do NOT add a budget entry for scripts/acceptance/scenarios/20-preview.helpers.ts — it must default-pass under the 500 threshold (confirm `wc -l scripts/acceptance/scenarios/20-preview.helpers.ts` < 500). Per Constitution Article VI, before declaring done run a repo-wide search for any reference to the old single-file assumption and the new path across ALL file types — `rg -n \"20-preview\\\\.ts|20-preview\\\\.helpers|scenarios/20-preview\" --hidden -g '!node_modules'` plus an explicit sweep of scripts/acceptance/run.ts, Dockerfile, docker-compose.yml, .github/workflows/*.yml, src/supervisor/ spawn/config strings, and docs/ — and fix any stale reference (file moves have broken production here before; encode the check, do not assume it). Verify: `bun run check:size` exits 0 AND `bun run check:dups` exits 0 AND `bun run check:all` is fully green (every gate stays green; the removed entry leaves both resulting files default-passing under the 500-line threshold).","labels":["ratchetwatch"],"blocks":[]}],"acceptance":["scripts/acceptance/scenarios/20-preview.ts is < 500 lines and its 658-line budget entry is gone from scripts/file-size-budgets.json; scripts/acceptance/scenarios/20-preview.helpers.ts is < 500 lines with NO budget entry (default-passes under threshold).","The `scenario` export and scripts/acceptance/run.ts `import { scenario as scenario20 } from \"./scenarios/20-preview.ts\"` are unchanged and resolve; `bun run typecheck` is clean.","No other scenario or file references a stale path — the Article VI repo-wide rg sweep (plus Dockerfile/compose/workflow YAML/supervisor strings/docs) shows no broken or stale reference.","`bun run check:size` and `bun run check:dups` exit 0 (no duplicated helper/type across the two files).","`bun run check:all` is fully green — no gate regresses and no release step is added (Article III: hygiene batches into the next real release)."]},"children":["warren-65f6","warren-0e37"],"createdAt":"2026-06-15T10:46:06.270Z","updatedAt":"2026-06-15T10:58:43.131Z","name":"Decompose scripts/acceptance/scenarios/20-preview.ts below the 500-line limit"} +{"id":"pl-3346","seed":"warren-832d","template":"feature","status":"approved","revision":1,"sections":{"context":"Plot plot-fbe17883 ('UI Nits') collects a batch of warren UI papercuts. Today sort behavior is inconsistent and duplicated: Runs.tsx ships a bespoke server-driven SortHeader/toggleSort with chevron icons and a 3-state cycle, while Workspace.tsx hand-rolls a client-side .sort() with handleSort/sortIndicator and text ↑↓ arrows and a 2-state toggle. Launching a plan-run is also a manual step: dispatch-plan-dialog.tsx and NewPlanRun.tsx both start planId as useState(\"\") and force the operator to paste the plan id by hand, even though NewRun.tsx already prefills plot_id from location.state. Several pages also have not been reviewed for mobile: wide tables and modals can produce horizontal scroll or trap the user with no way to dismiss. This plan eliminates the duplication and the manual steps so tables and run-launch flows behave consistently.","approach":"Land one shared sortable-table primitive (a useSortableTable hook plus a SortableHeader component under src/ui/src/components/ui/) that owns sort key/dir state, the click-to-cycle decision, and a single consistent chevron affordance, with a mode for server-driven sorting (Runs passes sort/dir to the API) and client-side sorting (Workspace sorts in memory). Migrate the existing pages onto it, deleting the per-page bespoke logic so common columns (date/started, status) read and behave identically everywhere. Separately, close the plan-run launch gap by reading the planner's emitted plan id (surfaced via the Plot's sd_plan attachment / planner run) and prefilling the planId field, mirroring NewRun's route-state plot_id prefill. Finally, do a mobile-readiness pass: audit every page for unintended horizontal scroll (the flex-main min-w-0 pattern, mx-a8a1df, plus the Table overflow-auto wrapper) and ensure every modal/popup is dismissible on a touch viewport. Keeping the primitive first lets the two table migrations land as small independent PRs.","alternatives":[{"name":"Per-page sort tidy-up without a shared primitive","rejected_because":"Violates the Plot constraint to prefer a single shared sortable-table primitive; leaves two divergent affordances (chevrons vs text arrows) and duplicated maintenance."},{"name":"Introduce a full data-grid/table library (e.g. TanStack Table)","rejected_because":"Heavy new dependency for a handful of small tables; would blow the bundle-size ratchet (check:bundle-size) and over-solves the papercut."}],"steps":[{"title":"Add shared sortable-table primitive: useSortableTable hook + SortableHeader component (server-driven and client-side modes) with unit tests","type":"feature","priority":2,"blocks":[2,3],"labels":["ui","tables"]},{"title":"Migrate Runs.tsx to the shared sortable-table primitive (server-driven mode): remove bespoke SortHeader/toggleSort, keep sort/dir API wiring and the 3-state cycle behavior","type":"task","plan_template":"refactor","priority":2,"labels":["ui","tables"]},{"title":"Migrate Workspace.tsx (and any other sortable tables) to the shared primitive: remove handleSort/sortIndicator text-arrow logic, standardize common columns (date/started, status) on the shared chevron affordance","type":"task","plan_template":"refactor","priority":2,"labels":["ui","tables"]},{"title":"Auto-populate plan_id after a planner finishes: read the planner run's emitted plan id (Plot sd_plan attachment) and prefill planId in dispatch-plan-dialog.tsx + NewPlanRun.tsx, mirroring NewRun's location.state plot_id prefill","type":"feature","priority":2,"labels":["ui","plan-runs"]},{"title":"Mobile readiness: audit every page at common mobile viewport widths and eliminate unintended horizontal scroll (flex min-w-0, table overflow wrappers, fixed-width layouts)","type":"feature","priority":2,"labels":["ui","mobile"]},{"title":"Mobile dismissibility: audit all modals/popups (dialog primitive, mobile nav drawer) so each is dismissible on touch — no state requiring refresh or re-navigation to exit","type":"feature","priority":2,"labels":["ui","mobile"]}],"risks":["tsc -b inside src/ui/ can fail with duplicate bun-types declarations from the parent node_modules (mx-ef8eab) — keep the primitive's types self-contained and run bun run build:ui before declaring done.","Server-driven (Runs) vs client-side (Workspace) sorting have different data flows; a single primitive must support both modes without forcing one page into the wrong one.","Discovering the planner-emitted plan id depends on the sd_plan attachment shape; if the attachment is absent the prefill must degrade gracefully to the current manual paste.","Mobile fixes touch shared layout (Layout main wrapper, Table) and can regress desktop — verify both viewports."],"acceptance":["Sortable tables across pages are driven by one shared primitive/hook; Runs.tsx SortHeader/toggleSort and Workspace.tsx handleSort/sortIndicator are removed.","Common columns (date/started, status) share identical sort affordances and behavior on every page that has them.","Triggering a plan run after a planner finishes auto-populates the plan_id field (mirroring plot_id prefill), with graceful fallback to manual entry when no plan id is available.","Every page is reviewed at common mobile viewport widths with no unintended horizontal scrolling.","Every modal/popup is dismissible on a mobile/touch viewport with no state requiring refresh or re-navigation to exit.","bun run check:all is green (lint, typecheck, check:dups, check:size, check:bundle-size, tests)."]},"children":["warren-2728","warren-c869","warren-3c6f","warren-ae9f","warren-4734","warren-6f43"],"createdAt":"2026-06-16T03:12:35.503Z","updatedAt":"2026-06-16T03:12:35.503Z","name":"UI Nits: shared sortable tables, plan_id auto-populate, mobile readiness"}