Workflow Bridge UI — phases 0–7 - #1040
Open
looksystems wants to merge 41 commits into
Open
Conversation
…low bridge UI Eight phase plans with an intent baseline, five pre-code specification artifacts (gate cards, session lifecycle, events, fixtures, attention policy), upstream proposal tracking, and the findings ledger from ten adversarial review passes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The design contract for phases 1-6: app frame, six screens, a 23-component catalog, and the visual language (provenance typography, gold-means-gate, per-thread density) - every element traced to an intent commitment. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per-element intent traceability, BatchScreenCard and PassThroughAsk, duplication and badge discipline, the keyboard model, the mobile sheet story, thread-model fork closed and spine aligned with the event spec, Tombstone/Archive split, BridgeBanner closed list. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…spine, watcher, store, version handshake, observability floor Implements phase-0-rails.md deliverables 1–5, 7–8 against specs 1–5: pnpm workspaces (bridge/app/shared); zod event vocabulary + gate-card + journal + config schemas (provisional until the end-of-Phase-2 freeze); in-process engine adapter via a thin child host with the width pin; pure spine(repo) over first-parent commits with occurrence-unique ids, stream epoch, and net-effect semantics; chokidar watcher with debounced live layer, durable increments, epoch-change re-baseline; SQLite schema (humans sentinel, cursors, read refs, sessions, gate ledger, comments, digests, push ledger, event log with a persistent seq counter); version handshake + migration posture (read-only banner, never runs migrations); /health, SSE events, structured logs, cost counters, debug console. 42 tests across both packages. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… docs Replay pauses at user-answer boundaries and always ends paused at the final one (Phase 2 adopt continues from there); offline mode consumes scripted answers with exact/key match assertions. Converter ingests both main-session and subagent transcripts (a subagent file marks every record sidechain). ui.yml runs typecheck + tests on every ui/** commit, never touching the product's gates. ui/README.md carries the phase-0 §5 packaging/discovery/config/API-key story. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…cts the recording flushed out The fixture is converted from a real terminal-driven workflow session (spec 4's Phase 0 form): a scratch project with the product installed was driven through /workflow-start → discovery → feature commit → discussion to a mid-flight stop — per-host limiter scope decided, background review returned two ask-lane findings, 'Work through them now?' gate pending. World snapshot = bundle + cache overlay (the lanes report rides the overlay); spine golden pins 13 durable events; regen-goldens.ts is the harness path — never hand-edit. Recording surfaced four real defects, all fixed with tests: replay's final boundary is the mid-flight assistant tail (outcome-keyed), not the last user record; the live baseline mixed git-blob and sha256 hash spaces, leaking spurious artifact.updated; head:null watcher seeds re-walked history against the tip snapshot; changes in chokidar's construct-to-ready gap were silently missed (ready() + sweep). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… gaps closed, specs amended Three parallel reviewers (spec fidelity, intent baseline, quality + security), every finding verified against the code before acceptance; full dispositions in ui/plans/REVIEW.md round 6. Headlines: bridge state moved out of .workflows/ (the one intent-constraint violation); shared reachability for both epoch-change paths; live-layer branch- switch race closed; live-only mode no longer forges a durable layer; debug-banner XSS fixed; engine calls time out; overlay tar path-safety; config file wired (width/port flow from it); CLI smoke test; offline answers derived from the recording by the harness; never-auto surface list extended with the render.cjs-verified finding-choice surface. Spec amendments: turn = human input (spec 1), artifact.updated's content-keyed exception stated (EVENTS.md). 62 tests, typecheck clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…Read lens) Bridge: engine-host render/detail methods (startOverview, workUnitStatus, epicDashboard, discoveryMap — the engine's own projections and derivations, never re-derived), the durable needs-you tier (reconcile, stale sources, triage, engine-derived blocks, report-pending), read-only /api routes with path-safety, static SPA serving. App: Vite/React/ Tailwind per specs/components-layouts.md — the frame (rail · surface · context panel), S1 lobby with the knowledge-not-ready and empty states, S3 channel (spine's admissible set only, commits exiled to the drawer, threads as one-line previews), S5 Read lens (react-markdown + Shiki + mermaid, firmness gradient, section anchors, 60ch measure), EngineEmbed verbatim embeds, BridgeBanner closed list, provenance typography, gold reserved and unused until a gate exists, cmdk palette (navigation only), light/dark. 72 tests across the three packages. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, firmness made visible, gold protected Headlines: epic channels no longer 500 (buildDiscoveryMap's real shape, pinned by an epic in the API test world); the percent-encoded wu-segment traversal and the symlinked-artifact read are closed with vector tests; lobby roadmap/baseline now come from the engine's own startDetail derivations; one staleness hop counts once. The firmness gradient is real chrome; SpineItem carries all four catalog variants incl. the gold gate ref; warn moved off the gold hue; mermaid securityLevel pinned strict; duplicate status line suppressed; attachDerived parallelised. Full dispositions in ui/plans/REVIEW.md round 7. 82 tests green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… corpus Spike (SDK 0.3.246, folded into session-lifecycle.md): width pin and BRIDGE_ID propagate; CLAUDE_PID/CLAUDE_CODE_SESSION_ID are harness- overridden — presence records carry the CLI's own identity, so the bridge reads them back and sweeps cleanup itself; resume keeps the sdk id stable (identity stays ours regardless); a disallowed tool denies inline, no hang. Parser (shared/gate-parser, pure, hash-injected): section extraction, the option grammar (glyph/cmd/range/prompt rows, NBSP continuations, strict deviation→refuse), normalizedBody, gate identity, kind taxonomy, never-auto with typed-confirm defaulting. Ask eligibility was CORRECTED by measurement — the engine's real rule is 'every MENU section stops unless the auto instruction says do-not-stop' (gateway menus and finding announce render plain; spec 1 amended, superseding wording recorded). Never-auto list gains the render.cjs-verified spec signoff surface. Authored adversarial corpus (truncated/reordered/paraphrased/ NBSP/two-menus/auto-mode) + a lane parsing the REAL recorded transcript's sections with byte-identical gate ids. 32 shared tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ary, queue, surface sweep Sessions (bridge/src/sessions.ts): one query per turn via resume behind a driver interface (real SDK + scripted test driver); every event tees to an append-only journal (synchronous — it is the re-derivation source); gates are PROJECTIONS re-derived from the journal, never a table (the ledger is audit); serialized answer path with CAS (races resolve visibly as resolved-externally, exactly one injection); answer-while-dead verifies the journal tail before injecting; health states incl. the spiked permission-denial detection; explicit end() (every SDK turn carries a result — trailing prose is a pass-through ask); restart restores gates from journals with identical ids; presence sweep reads identity back from records per the spike. Boundary (auth.ts): Host allowlist (DNS rebinding), Origin allowlist, per-install 0600 bearer token on every mutation and the event stream. Lease (lease.ts): O_EXCL + engine-style stale-break; the loser runs as a read-only mirror with a banner. Queue (queue.ts): the two honest tiers joined under spec 5's sort. Sweep (sweep.ts): the engine's MENU surfaces enumerated from source — a test pins NEVER_AUTO_SURFACES over every AUTO_OVERRIDE_LINE site; allowlist GENERATED from skills' frontmatter (paren-aware). API: queue/sessions/thread reads, token- gated session-start/answer/end mutations. 123 tests, typecheck clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… hosting GateCard per the catalog: product-terms ask with the gold glyph, option buttons with mono key chips (recommended marked, NEVER pre-selected — initial focus is the free-text input), one answer path (key tap, typed key from a focused row, or free text — all ordinary user turns), typed-confirm never one-taps, relay-divergence notice, inert resolved states. S2 queue: spec-5 order, both row anatomies (live ask / durable flag), card overlay with channel context, pass-through rows open the thread (no fabricated card), durable rows navigate to their resolving surface. S4 thread: provenance-typed journal rendering (serif conversation, mono engine sections, DATA never displayed), resolved cards collapse to a line, PassThroughAsk reply box, session health badge + end-session. Lobby hosts the single shaping thread; channels get drive/open-session. Rail + channel badges derive from the queue's own rows (gold dot = live gate). 17 app tests; build clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e, presence strip, checkpoint decisions The end-to-end quick-fix ran from the bridge: captured by chatting the capture skill in the lobby thread, promoted from the inbox working set, shaped (MENU: shape gate), scoped, implemented (task gates; a/auto exercised so auto-mode menus landed in the journal), reviewed clean — unit completed, the 250ms retry delay landed in src/client.js with real commits. Structured, typed-confirm (archived-item permanent delete, via the never-auto label pattern) and free-text answers all rode the one answer path. kill -9 with two gates open → restart → identical gate ids re-derived from journals. Terminal presence on an epic topic coexisted with the running bridge session; the mirror carries held/live rows and strikes held topics. Cost counters surfaced the run (.40). The run measured the permission stack into shape (each step recorded in the spec/round 8): Bash(cmd) grants expand to Bash(cmd:*); acceptEdits over default; dot-path writes are sensitive-file-gated above declarative rules AND bare allowedTools names shadow canUseTool — so the bridge owns a programmatic canUseTool policy (files within the project, Bash by generated prefix incl. the BASE_BASH dev set, everything else denied with the reason surfaced). PresenceStrip + channel presence join + held-topic strike-through. Checkpoint decisions recorded in the plans: custom-events-over-SSE (phase-0's own criterion fired), and neither assistant-ui nor CopilotKit — the journal renderer is the surface. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…dened, 12 defects fixed Three reviewers (fidelity ×2, intent+security). Full dispositions in ui/plans/REVIEW.md round 8. Security headline: the Bash allowlist was a naive startsWith — 'git diff && curl evil' passed. Now bashCommandAllowed rejects command/process substitution, splits on every shell control operator, and requires EVERY segment allowlisted (injection-vector tests). WebFetch blanket-allow removed; Write/Edit realpath containment (symlink escape closed, matching round 7's read-path fix); timing-safe token compare; session env redacts non-Anthropic secrets; the allowlist is Bash-only (file tools governed by canUseTool, the dead entries gone). Fidelity/intent: BatchScreenCard shows the finding batch (was answerable unseen); MENU:cancel gate is never-auto (was one-tap destructive); prompt-fallout flips to errored; the read-only-mirror banner surfaces; injected turns tagged with the gate id; out-of-scope-bank + consult stale-source queue rows; gateType mapping completed; truncated menu → pass-through not a fabricated card (N3); the lease moved out of tree (N1, was leakable to fixtures). Gaps closed: bridge-side one-session- per-address, 4h idle sweep, buildOrderPos, NEEDS YOU lobby strip, thread S4 links, 'bridge record-moment', resuming state, allowlist+policy test lanes, n=2 restart test. One fidelity fork misread this session's own in-flight folds as unauthorised edits — recorded in the ledger; no revert, round 8 is the correct number. 149 tests, typecheck clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ests, coordinator Pure attention policy (attention.ts) implementing spec 5's tables: laned findings (any walk-lane → push once at report-landing, batch-only → badge+digest; app-connected downgrades to alert, quiet hours accrue); laneless gate-type table (typed-confirm/consult/replan/signoff push, task-loop/bootstrap badge, batch-screen digest, conflict pushes only when it blocks alone, escalation overrides all). The lane mini-extractor (lanes.ts) reads apply/decide/route from report markdown — anything else or a present-unreadable file is walk; ENOENT is not-present. The durable notifier (notifier.ts): at-most-once per (rowKey, contentHash), a restart re-pushes nothing, quiet-hours accrual → morning roll-up, T_roll collapse. EscalationTracker: once per attendance, re-armed by activity, navigation grace. Digests (digest.ts): landed/waiting/next, lobby strip waiting-suppressed. The coordinator (attention-coordinator.ts) binds all to live gate events + a 30s tick (report scan, escalation clock, morning roll-up, digest rebuild). Cursors/read-refs go live (read-ref recorded on artifact view, HEAD-at-render for Phase 4). /api/activity, /api/digests, web-push sink. 17 attention tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…AY strip, activity + watchdog DigestCard (S6 as a card, waiting-suppressed in the lobby strip); EscalationChip (idle age, escalation + quiet-hours markers, a state not a countdown). The lobby TODAY strip (digest composition). App reports app-connected + focused-thread activity (route-derived) for time-based thread-scoped suppression, and runs a health-heartbeat watchdog that raises the bridge-unreachable banner when health goes stale >90s. The queue shows the escalation chip on live rows. Read-refs recorded on artifact view. 20 app tests; build clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Drives the multi-unit event script through the real Notifier + policy: three units in flight → exactly the signoff + walk-report pushes (≤5, each actionable), everything else badge/digest and reachable via the queue floor; the overnight case (2am walk report accrues, one morning roll-up); restart re-pushes nothing; notifications-off still records every decision (queue is the floor); an unrecognised lane pushes as walk. Live-verified against the running bridge on the fixture world: at 2am the restored walk-lane review report accrued (not pushed) — the overnight case working in anger — and digests built for all three channels. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…led, 9 defects fixed Both reviewers converged: the pure policy was correct and tested, the coordinator wiring feeding it live data was broken and untested. The fold overhauls the coordinator and adds a coordinator-level test suite. Fixed: queue escalated now joins EscalationTracker (spec 5's leeovery#1 sort key was dead); digest 'next' renders the engine's own next-phase surface; the notifier no longer conflates a quiet-hours-deferred push with a batch digest (quiet-hours accrual moved into the notifier — batch never pings); escalations are exempt from the T_roll collapse and an intraday roll-up drains bursts (an escalation could be swallowed till morning); the morning day-guard uses local date (UTC/local mix double-fired daily outside UTC); report rowKeys carry the phase and hash the file path (cross-phase reports suppressed each other); the activity heartbeat is visibility-gated and heartbeat≠interaction (a backgrounded tab silenced pushes and re-armed escalation); gateCeremony's engaged→none restored; blocksWithNothingElse checks all open gates. Hardening: HEAD-sha cache, report size cap + seenReports eviction. Full dispositions in REVIEW.md round 9. 181 tests; typecheck clean; app builds. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Structure comes from where it lives (corrected premise): discussion subtopics + lifecycle from the manifest (never session cache), spec sources AND consult_references from the manifest, investigation/review heading-keyed (stable templates), research Read-only (low structure is honest). Graceful degradation everywhere — no headings/no manifest → Structure absent, never an error. Claim-chip finder (cmd → result convention, best-effort, misses cost nothing) with anchors matching the Read lens's own slug algorithm exactly. History lens: git file timeline + what-moved diff from the newest recorded read-ref to HEAD, with the epoch-break 'lost' state (visible degradation, never a wrong diff). /api/history, /api/roadmap, and structure+whatMoved folded into the artifact view (ref read before the view overwrites it). 7 structure tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…what-moved, roadmap, verdict/brief S5 gains Read / Structure / History. Structure is ABSENT (never an error tab) when unavailable; the rail is per-type — MapRail from manifest subtopics for discussions, SourcesPanel (sources AND consult references, both blocking sign-off) + a contents rail for specs, heading rails for investigation/review/brief. ClaimChip renders the recorded command → its result with a COPY button and no re-run (N5; verification badge sourced from the review report only). WhatMovedRibbon is an overlay chip → the git diff since the last read-ref, with the epoch-break 'lost' state (never a wrong diff). History lens shows the file timeline. Roadmap surface: horizons/items/lifecycle/origin/sessions, read-and-navigate, with links into pulled work units. VerdictBanner (review verdict-first) + BriefBadge (regenerable — not a record). Rail Roadmap link + route. Anchors match the Read lens slug exactly. 28 app tests; build clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…done-means
For an epic the topic differs from the work unit; the artifact path
carries it (phase/{topic}/…). buildStructure now derives the topic from
the path so an epic spec's sources + consult references join correctly.
Verified live: the payments-epic billing-model spec is decidable in the
browser — sources (incorporated/pending) and consult references live
from the manifest, the measured claim rendered as a chip; the what-moved
lens shows the diff of a post-read amendment; the freeform discussion
gets its 4-subtopic rail from the manifest.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… fixed, exit decision recorded Two reviewers. Defects: epic discussion structure (flat-file topic derivation), the Roadmap reading nonexistent fields (now the engine's own state/work_unit — a 'renders from engine' fix), the read-ref consumed by background refetches (now a deliberate POST /api/.../read only on a focused view), the VerdictBanner keyword-sniff (now the template's **Verdict** line + buckets), unknown-horizon items vanishing, the dead BriefCard session-log link + positional panels. Gaps closed: claim-verification badge (review-report-sourced only), spec claim rail + review verdict/buckets, baseline layers coloured, the 60ch measure + rail-collapses-first, pending fail-safe default, gate deep-link scroll+highlight. Hardening: /api/history realpath containment, artifact size cap, git leading-dash/non-hex rejection. Full dispositions + the exit decision (live discussion map: kept CLOSED, no dogfooding evidence asked for it) in REVIEW.md round 10. 205 tests; typecheck clean; app builds. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…etry + plan DAG readers The source inventory (specs/telemetry-source-inventory.md) maps every displayed datum to its manifest field or git fact BEFORE the surface — the anti-scrape rule. buildTelemetry reads current task/phase, completed phases/tasks, status, consolidation (gate mode / bank / staging / consolidated phases), commits landed (from the durable store), and agents-active — all manifest+git, no transcript. dep-blocked comes from the engine's own derivation. The plan DAG reads the lowest-common model (tasks+deps+status) per adapter: local-markdown parses task-file frontmatter (fixture-pinned), tick reads its CLI graph (stubbed — not installed in the prototype), Linear degrades to link-out. Channel view carries telemetry per implementation topic + planFormat; /api/plan route. 5 telemetry/DAG tests. (No attempt-counter invented — it has no durable field; recorded in the inventory as a possible upstream.) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ard, PlanDAG; inventory correction The Delivery section on the channel (only when an implementation topic runs, collapsed by default, never animates): TelemetrySurface shows current task/phase, completed phases/tasks, fix-attempts + analysis cycles, dep-blocked ⚑, the consolidation gate ◆, agents-reading, and commits — all from the manifest. ConsolidationCard renders the bank + staged tasks as one decide-shaped read view (answering rides the session's gate). PlanDAG lays the local-markdown tasks by dependency depth in plain CSS (no heavy graph dep for a read-only scannable view), degrades a Linear plan to a link-out. Verified live: the completed quick-fix's telemetry (phases 1,2 done, consolidation gated) and its 2-task local-markdown DAG both render from the manifest/files. Inventory corrected: fix_attempts + analysis_cycle_total ARE durable fields (verified against the real manifest) — shown directly, not invented. 216 tests; build clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…}.tasks.{n}
The manifest's staging is staging.{cycle}.tasks.{n}=<decision> (a
nested object per fields.cjs), not the array CLAUDE.md's shorthand
suggested — buildTelemetry's Array.isArray check made staging always
empty. Now flattens each cycle's task decisions to a list; the
ConsolidationCard renders task + decision. Verified live: c1 shows
task 1 approved, analysis cycles 2. (Caught pre-emptively from the
round-11 prompt.)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…coped agent count, dag hardening
- planFormatOf resolves the requested topic's format (was: first planning item)
- countAgents scoped to the implementation phase; countAgentsAllPhases for the header chip
- readLocalMarkdownDag skips symlinks + caps at 1MB (mirrors round-10 hardening)
- channel Delivery detail moved behind each topic's collapsed TelemetrySurface (kills the S3 seam)
- plan DAG degrades honestly on a dependency cycle; natural task-id sort
- staging shape corrected to staging.{cycle}.tasks.{n} across code, inventory, plan
- tests for symlink-skip, cycle banner, natural sort; REVIEW.md round 11 ledger
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…sence, comments Backend (bridge): - identity.ts: single-user sentinel (zero-config) + github-mode member check (real GitHub push-access API, fail-closed), server-side cookie sessions - ownership.ts: gate ownership as UI-side routing (driver→channel-default precedence, claim/reassign, 24h-stuck, mayAnswer, externally-resolved join) - capture.ts: ephemeral headless capture-skill session, durable-fail lobby row - presence-humans.ts: humans-viewing heartbeat + inferred working sessions - comments.ts: threads on gates/artifacts, unread ceremony, per-viewer reads - db.ts: Phase 6 tables (auth_sessions, session_drivers, gate_owner_activity, channel_defaults, human_presence, failed_captures, comment_reads) + guarded humans.github_login column - api.ts: whoami/login/logout, claim, capture, comments routes; ownership + unread overlay on queue/channel/thread gates; per-human read-refs - cli.ts: wire Identity + CaptureRunner, assign owner on gate open Frontend (app): - GateCard: owner badge, watcher read-only, claim, unread-comments ceremony blocking the confirm, quote-to-draft, "answered outside the UI" - GateComments, CaptureButton (optimistic ack), FailedCaptures, IdentityBadge - PresenceStrip: three honest kinds (viewing / working / inferred) - Queue rows: owner/stuck/unread cues; openStream degrades without EventSource 247 tests green; capture gesture verified live (wrote inbox idea + provenance). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ness, capture attribution
Security/correctness (with tests):
- capture retry that fails again no longer duplicates the lobby row (discard-first)
- github-mode membership now gates writes: a non-member is a read-only watcher
- capture concurrency cap (4-slot) + capture-scoped Bash allowlist (least privilege)
- non-member login returns 200 (authenticated watcher), not a cosmetic 403 w/ cookie
- externallyResolvedAt: sign-off resolves only on topic-addressed phase.completed
(was falsely resolved by the topicless workunit.status-changed)
- "answered outside the UI" now applies on every surface (thread/queue/channel)
- /api/sessions openGate enriched → queue overlay shows watcher/unread ceremony
- inferred-sessions reads state.json mtime (presence files only exist for
research/discussion) — the kind was always empty; verified live
- capture stamps the authenticated author into provenance + body; message-level
capture-from-message affordance in the thread; verified live ("by You")
- spec-5 queue default view = mine + unowned + stuck (github mode)
Intent/UX:
- ownership overlay suppressed in single-user (zero-config, identity invisible)
- artifact comment threads on the Artifact screen; claim wired into session thread
- watcher/blocked option rows disabled not dead; gold no longer leaks to comment/
capture chrome; failed-capture rows show the error; whoami sends the real repo
Deviation recorded in phase-6 §1: identity+member-check contract shipped, not the
OAuth redirect flow (deployment wiring) — identity is attribution not
authentication, bearer token stays the transport boundary. REVIEW.md round 12.
249 tests green; capture author + inferred-presence fixes verified live.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…resources, parity - bridge/src/mcp.ts: a third client of the bridge. JSON-RPC-over-stdio MCP server (no new dependency), tools (workflow_state/queue/open_gate/answer), ui:// gate-card and queue resources rendering the frozen shared card schema — no second card logic - bridge/src/mcp-client.ts: HTTP BridgeClient, authenticated as a named human (install bearer + per-user Phase-6 cookie) — reuses the one answer round-trip - cli.ts: `bridge mcp` stdio entry (secrets from env, never argv) - Typed-confirm attestation enforced at the bridge (single point): a never-auto gate accepts an answer only with an explicit ui-gesture attestation (SEP-1865); a plain model tool call is rejected with a deep link. SPA answers attest. - The MCP card renders typed-confirm read-only; open-gate comments render read-only Parity tests: same fixture gate via SPA and MCP -> byte-identical answers + identical ledger rows; negative: a model tool call answering a typed-confirm gate is rejected (and the raw endpoint rejects it too); A-owned gate refuses B's MCP submit. Verified live: stdio initialize/tools-list/workflow_state through the running bridge. 258 tests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…attestation honesty - ui:// resources gain a host-bridge script producer (OpenAI Apps / MCP-UI postMessage, feature-detected): tap gates + queue rows are interactive; typed-confirm stays read-only (never originated from the widget) - runStdio caps the stdin buffer at 4MB (was unbounded — newline-less DoS) - typed-confirm attestation guard looks the gate's confirm up by id (live card or ledger), independent of how the holder resolved - MCP answers recorded as via:'mcp' in the ledger (was 'ui' — audit trail now distinguishes MCP from SPA; provenance != parity; test asserts it) - resources/read serves a gate-card template (list/read now consistent) - initialize echoes the client protocolVersion; notifications get no response; mcp-client encodeURIComponent's the gateId; github-mode-no-cookie warning - attestation comment made honest: it's a host trust assumption the bridge cannot verify; the shipped widget keeps typed-confirm read-only so weakening it needs both a mis-implementing host AND a prompt injection 262 tests green. REVIEW.md round 13. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… via browser test A round-12 edit placed `useState(note)` AFTER the `if (!thread) return` guard, so the loading render ran one fewer hook than the loaded render — React's hook-count invariant broke and the thread screen (and, with no error boundary, the whole app) rendered blank. Moved the hook above the guard. Caught by driving the live SPA with agent-browser (the /s/:id route was blank with "Minified React error leeovery#310"); unit tests missed it because they didn't render SessionThread through the null->loaded transition. Added a test that does. 263 tests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…oadmap Both found by driving the live SPA with agent-browser. - CaptureButton opened its fixed-width popover with `right-0` from a left-rail button, so it extended ~85px past the left viewport edge (invisible). Added align/direction props (default left/down); the rail button opens upward-right so it never clips. Toast repositioned to match. - The ⌘K palette hardcoded only a Lobby item (+ channels), omitting Queue and Roadmap. Now lists all three top-level rail destinations plus channels. Tests: SessionThread-style jsdom shims (ResizeObserver/scrollIntoView) for the cmdk palette; palette lists+navigates; capture popover alignment classes. 266 tests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…to cache)
The four bespoke chat inputs (thread reply, gate-card free-text, comments) are
consolidated into one <ChatInput> with a consistent keyboard model (Enter sends,
Shift+Enter newline), disabled/busy states, and optional controlled mode (so the
gate card keeps quote-to-draft). CaptureButton stays a form (kind selector).
File attachments (pick / paste / drop) materialize into the gitignored cache and
are referenced as [attached: <path>] in the turn, so the session's existing Read
tool picks them up — no new session round-trip, no forking the product model.
Backend: POST /api/attachments writes to .workflows/.cache/{wu}/attachments (or
.uploads/{session} for the lobby) — the ONE deliberate, documented exception to
"the bridge writes nothing to .workflows/", justified because it's transient,
gitignored, user-input-in-transit (not bridge state) in the purgeable cache.
Hardened as a file-upload sink: name sanitized to a safe basename, random prefix
(never clobbers), 10MB cap, realpath-confined under .cache/, ids validated.
Attachments enabled on session-answer inputs (thread reply, gate answer);
comments are text-only. Verified live: upload round-trip (traversal stripped,
path-safe, gitignored), oversize→413, the reply box renders the shared input
with the attach affordance.
278 tests green (8 upload-security, 4 ChatInput).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…adBody hang, quota Adversarial review of the new file-upload sink. Traversal/containment core confirmed sound; folded: - the attachment route now applies the github-mode membership gate (was returning before it — a non-member watcher could write files) - readBody settles on every terminal event (req.destroy fires close/error, never end — the promise hung and pinned the buffered body); unit-tested - a per-target attachment cap (200 → 429); dead base64 catch removed; import node:crypto (was the unimported Web Crypto global) REVIEW.md round 14. 284 tests green (13 attachment). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
After sending an answer/reply the bridge awaits the whole session turn (often many seconds); silent waiting read as frozen. A small, scoped spinner + "the session is working…" now shows during that window in the thread reply and the gate card, gone the moment the response lands. A deliberate, requested exception to the codebase's "no spinner theatre" motion rule — that rule governs AMBIENT surfaces; this is transient feedback for a synchronous action the human just triggered, not an attention-grab. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A session cut off mid-turn (a bridge restart/crash — its journal tail is a tool-result, not an ask) restores as 'dead' with no open gate. The thread only renders an input when there's a gate, so such a session was a dead-end: the SessionHealthBadge said "resumable" but offered no way to resume it. - SessionManager.resume(id, text): runs a fresh turn resuming the recorded SDK session, no gate CAS (there is no gate); refused while live/ended - POST /api/session/:id/resume - SessionThread shows a resume ChatInput (with attach + working spinner) when the session is dead/errored with no open gate Verified live: the interrupted demo session now shows "This session was interrupted — send a message to resume it." with a resume box. Tests: bridge resume (dead→turn runs, resumes the sdk session), SessionThread resume box. 286 tests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ui/demo is a scratch workflow project used for live-testing the bridge — not part of the UI deliverable. Ignoring it prevents its .workflows/ state (and a binary knowledge store) from being swept into commits. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The README was frozen around phase 0/1 (app "empty shell", shared "provisional", "no session driving"). Refresh it: the SPA is built and is the primary UI (build + open http://127.0.0.1:4870/), sessions are driven, and add the phase-6 auth modes, the phase-7 `bridge mcp` invocation, the chat-attachment cache exception, and `pnpm build` to the test/run steps. The accurate parts (engine discovery, width pin, config, out-of-tree state, version handshake) are unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three self-contained HTML design/presentation pages under ui/artefacts/ (the claude.ai publish-time frame runtime stripped so they render standalone): the-workflow-bridge (overview), workflow-bridge-screens (screen mockups), and agentic-workflows-field-guide. Linked from the README. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Assistant messages were shown as raw markdown source (**bold**, > quotes, ``` fences all literal — caught in the browser). Render them through the existing Markdown component (react-markdown + remark-gfm + Shiki + mermaid) with the read-lens prose styles. Engine sections stay verbatim mono (tool-result → EngineEmbed) — the two are now visually distinct. Test added. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Workflow Bridge UI — phases 0–7
Builds the Workflow Bridge UI end-to-end from the committed plan set in
ui/plans/: a read-only-first veneer over.workflows/+ git + the engine's in-processlib.cjs, adding zero new workflow state — state renders from the engine, gates are never guessed, and never-auto gates get a typed confirm.Delivered as a pnpm monorepo under
ui/(bridge/·app/·shared/), each phase built → done-means verified → adversarially reviewed (findings verified against the code before folding) → ledgered. The full review history is inui/plans/REVIEW.md(rounds 6–14).Phases
Follow-ups (this branch, post-phase)
Shared
ChatInput+ file attachments (materialized to the gitignored cache, referenced by path so the session's Read tool consumes them); a calm working indicator for the answer-in-flight moment; a resume affordance for interrupted sessions; and four bugs surfaced by driving the live SPA in a browser (aSessionThreadhooks-order crash, a clipped capture popover, an incomplete palette, and a membership-gate bypass on the upload sink).Quality
shared34 ·app57 ·bridge195), all packages typecheck (tsc --noEmit), app builds.Notes
ui/demo(a local scratch workflow project used for live-testing) is excluded and gitignored — not part of the deliverable.ui/plans/UPSTREAM.mdand are not implemented here (they await maintainer decision).🤖 Generated with Claude Code