FE-1115: Refine Pi TUI component DX: preview harness, component refinements, and new components#281
Conversation
b6adbbb to
e293ee7
Compare
PR SummaryMedium Risk Overview Introduces shared layout primitives — Retires the unwired Reviewed by Cursor Bugbot for commit 40ece5c. Bugbot is set up for automated code reviews on this repo. Configure here. |
There was a problem hiding this comment.
Pull request overview
Adds a standalone real-terminal preview harness for .pi/components (no workspace/session/DB), expands preview coverage to additional presentation lanes (message renderers + persistent chrome), and lands reusable bordered-presentation primitives (rounded box + scroll viewport + wheel decoding) that are exercised in both the harness and existing components.
Changes:
- Introduces
npm run dev:componentsharness (gallery + per-entry registry) with a realThemeandKeybindingsManager, plus dev-facing docs/topology updates. - Adds shared presentation primitives in
.pi/components/(projectRoundedBox,projectScrollViewport,parseWheelEvent) and refactorscards+workspace-dialogto use them. - Retires the unwired
tui-labextension registrar while keepingTuiStyleLabComponentas a previewable reference component; adds/updates tests across the new primitives and harness tiers.
Reviewed changes
Copilot reviewed 43 out of 44 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/session/TOPOLOGY.md | Documents exchange-broker scope and links to the structured answering-paths mechanism doc. |
| src/rpc/TOPOLOGY.md | Clarifies structural split between RPC submit vs live broker answering. |
| src/dev/TOPOLOGY.md | Adds dev-tooling ownership and documents the component preview harness. |
| src/dev/index.ts | Exports the component preview runner from the dev entry surface. |
| src/dev/component-preview/theme.ts | Creates a real Theme + EditorTheme adapter for preview rendering. |
| src/dev/component-preview/static-preview.ts | Adds static preview helpers for message-renderers and persistent chrome components. |
| src/dev/component-preview/registry.ts | Registers preview entries and mirrors production presentation contracts per component. |
| src/dev/component-preview/gallery-component.ts | Implements the interactive gallery menu component for choosing preview entries. |
| src/dev/component-preview/custom-ui.ts | Implements ctx.ui.custom-like preview wrapper including optional wheel-scroll translation. |
| src/dev/component-preview/tests/workspace-dialog-scroll.harness.test.ts | End-to-end harness test for the real workspace-dialog-scroll registry entry. |
| src/dev/component-preview/tests/theme.test.ts | Verifies preview theme emits expected 256-color ANSI and is usable by a real component. |
| src/dev/component-preview/tests/static-preview.test.ts | Tests static preview mounting + renderer capture for transcript message lane. |
| src/dev/component-preview/tests/custom-ui.test.ts | Tests overlay/inline behavior and wheel-scroll lifecycle + routing. |
| src/dev/component-preview.ts | Public harness entry: real terminal + TUI boot, deep-linking, and gallery loop. |
| src/.pi/extensions/TOPOLOGY.md | Removes tui-lab registrar from extensions inventory and explains retirement. |
| src/.pi/extensions/exchanges/TOPOLOGY.md | Links to the new structured answering paths mechanism doc. |
| src/.pi/components/workspace-dialog/index.ts | Re-exports WORKSPACE_DIALOG_MAX_VISIBLE_OPTIONS from the component module. |
| src/.pi/components/workspace-dialog/component.ts | Adds scroll-windowing + thumb rendering via new primitives; introduces fixed max-visible constant. |
| src/.pi/components/tui-lab/style-lab-component.ts | Removes registrar wiring; keeps TuiStyleLabComponent as reference-only component. |
| src/.pi/components/tui-lab/index.ts | Exposes TuiStyleLabComponent from the tui-lab public seam. |
| src/.pi/components/TOPOLOGY.md | Updates components layout + test tier descriptions; notes harness home under src/dev/. |
| src/.pi/components/scroll-viewport.ts | Adds pure scroll-viewport/windowing primitive with thumb-row reporting. |
| src/.pi/components/rounded-box.ts | Adds pure rounded-box projection primitive with label + thumb-row support. |
| src/.pi/components/mouse-wheel.ts | Adds pure SGR wheel-event decoder for preview harness wheel passthrough. |
| src/.pi/components/cards.ts | Refactors card border rendering to delegate to projectRoundedBox. |
| src/.pi/components/brunch-editor.ts | Adds bordered editor chrome projection + experimental BrunchEditorComponent. |
| src/.pi/components/tests/workspace-dialog.test.ts | Adds tests proving windowing, selection-follow, and thumb folding for long lists. |
| src/.pi/components/tests/tui-lab-style.test.ts | Keeps palette/primitive tests; drops registrar/bundle-exclusion tests after retirement. |
| src/.pi/components/tests/tui-lab-cycle.test.ts | Fixes imports after moving TuiStyleLabComponent under components. |
| src/.pi/components/tests/scroll-viewport.test.ts | Unit tests for projectScrollViewport behavior and thumb math. |
| src/.pi/components/tests/runtime-posture-axis-picker.harness.test.ts | Aligns harness presentation with production inline-swap behavior; updates assertions. |
| src/.pi/components/tests/rounded-box.test.ts | Unit tests for projectRoundedBox borders, labels, truncation, padding, and thumb rows. |
| src/.pi/components/tests/mouse-wheel.test.ts | Unit tests for SGR wheel decoding behavior. |
| src/.pi/components/tests/cards.test.ts | Updates/locks card rendering expectations after rounded-box refactor. |
| src/.pi/components/tests/brunch-editor.test.ts | Unit tests for bordered chrome projection, padding, autocomplete trailing rows, and links. |
| src/.pi/components/tests/brunch-editor.harness.test.ts | Harness tests for editor behavior through real TUI input routing and keybindings. |
| src/.pi/tests/support/virtual-terminal.ts | Adds a writes log to assert terminal write side-effects (mouse DECSET toggles). |
| scripts/dev-components.ts | Adds the script entrypoint for npm run dev:components. |
| package.json | Adds dev:components script. |
| package-lock.json | Adjusts pi-ai bin path to ./dist/cli.js. |
| memory/PLAN.md | Updates planning state: completes component-dx slices, introduces bordered-chrome-production frontier. |
| docs/design/STRUCTURED_EXCHANGE_ANSWERING_PATHS.md | New mechanism doc describing the three exchange answering paths and coverage matrix. |
| docs/archive/PLAN_HISTORY.md | Archives trimmed PLAN history entries and retired frontier-definition detail. |
| .agents/skills/planning-pr/SKILL.md | Removes the local planning-pr skill definition file. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| '', | ||
| this.theme.fg('dim', '\u2191/\u2193 or j/k move \u00b7 enter opens \u00b7 q quits'), | ||
| ]; | ||
| return lines.map((line) => line.slice(0, safeWidth)); |
| import type { KeybindingsManager, Theme } from '@earendil-works/pi-coding-agent'; | ||
| import { type Component, type TUI } from '@earendil-works/pi-tui'; |
| `npm run dev:components` (or `npm run dev:components:watch` for a `tsx watch`-backed edit loop) boots a | ||
| real `ProcessTerminal` + `TUI` and shows a gallery of every registered `.pi/components` entry | ||
| (`src/dev/component-preview/registry.ts`) — no seeded workbench, session, or DB required, since these | ||
| components are render-only with injectable `theme`/props. |
| tui.start(); | ||
| await entry.open(tui, theme, keybindings); | ||
| tui.stop(); | ||
| return; |
npm run dev:components boots a real ProcessTerminal + TUI and shows a
gallery of every registered .pi/components entry - no seeded workbench,
session, or DB required, since these components are render-only with
injectable theme/props.
Each registry entry mirrors its component's real production presentation
contract (overlay-with-options vs. inline editor-swap), reimplementing
ExtensionUIContext.custom's documented calling shape rather than assuming
every component is an overlay. Theme is a real pi-coding-agent Theme
instance, not a duck-typed stand-in.
- src/dev/component-preview/{theme,custom-ui,registry,gallery-component}.ts
- src/dev/component-preview.ts (public entry) + src/dev/index.ts export
- scripts/dev-components.ts, package.json dev:components[:watch]
- src/dev/TOPOLOGY.md + src/.pi/components/TOPOLOGY.md updated
- memory/PLAN.md: component-preview-harness (Parallel / Low-Conflict,
tooling, no Linear/branch by AGENTS.md convention)
- memory/cards/tooling--component-preview-harness.md: design rationale
Named but not fixed here: runtime-posture-axis-picker.harness.test.ts
wraps the picker in showOverlay when production uses inline-swap;
tui-lab's slash command remains unwired in pi-extensions.ts.
The harness test wrapped the runtime mode picker in tui.showOverlay(...), but production (commands/index.ts's openModePicker -> ctx.ui.custom with no overlay options) presents it inline via tui.addChild + tui.setFocus. Drift called out in the component-preview-harness card. Switching presentation modes surfaced a second, related bug: the active-segment assertions checked for a doubled *trailing* space after the label, which only held because the overlay box's fixed-width fill padded the line further. In the real inline-swap render the last track segment gets no such fill, so the doubled-trailing-space check was a false invariant. Replaced with a doubled-*leading*-space check, which the badge + separator/padding stacking produces reliably regardless of a segment's position in the track. Also tightens src/.pi/components/TOPOLOGY.md's harness-tier description, which generalized "drive through overlay" for all harness tests -- not true for inline-swap components like this one.
…s reference registerBrunchTuiLab (the /brunch:tui-style-lab command) never entered the product extension bundle (proven by its own test suite) and was inert even under Pi's ambient .pi/extensions/ directory scan, since ambient loading calls default exports with no options argument and the registrar's own enabled guard defaulted to false. It has had no working invocation path since it landed in FE-845. TuiStyleLabComponent (the actual render-only Component) moves to .pi/components/tui-lab/style-lab-component.ts, matching the directory's own ownership rule (components/ owns render-only Pi TUI components, extensions/ owns wiring). It stays previewable and visible as a design reference via 'npm run dev:components -- tui-lab', with no production call site. Split the extension-side test file: palette-rendering assertions move to components/__tests__/tui-lab-style.test.ts; the cycle-demo component test already lived under components/ and just gets its import path fixed. Registration-behavior tests (enabled-gate, product-bundle-exclusion) are dropped since the registrar they proved no longer exists. Resolves the second component-preview-harness carried-forward finding.
Audited every .pi/extensions/* registrar (all 19 wired to src/app/ pi-extensions.ts) and every .pi/components/* export (all consumed) against real call sites -- tui-lab was the only orphan, already retired. Cross-checked .pi/extensions/TOPOLOGY.md and src/dev/TOPOLOGY.md layout/path claims against the real directory shape -- both already accurate. One drift remained: .pi/components/TOPOLOGY.md's layout sketch still named runtime-posture/strategy-picker.ts, retired in the prompt-axis retirement (fc1b016). Only axis-picker.ts remains under runtime-posture/.
The harness previously only covered ctx.ui.custom (overlay/inline). Two more real presentation contracts existed in .pi/components/ with no preview path: - pi.registerMessageRenderer (alternatives-card-set): a pure (message, options, theme) => Component function with no done() callback. static-preview.ts's captureMessageRenderer() feeds registerBrunchAlternatives a minimal fake ExtensionAPI slice to capture the real renderer closure -- same technique registry.test.ts already uses to assert registration, just taken one step further into actually rendering the output. - ui.setHeader (BrunchStartupHeader): turned out not to need new plumbing at all -- it's already a plain Component with a public constructor, so it slots into the same inline-swap machinery as any other static content. Both mount via previewStaticComponent(), which wraps the static component with a dismiss-on-any-key handler -- a preview-only affordance, since neither lane has a real dismissal in production (a transcript message persists; chrome stays mounted for the session). Footer (ui.setFooter) is deliberately deferred: it's driven by live token/model/coherence state rather than static layout, and rides the scope the user wants to refine later. Smoke-tested both new entries end-to-end via npm run dev:components (gallery listing, deep-link, and gallery round-trip) using a real PTY, not just the VirtualTerminal-backed unit tests.
…chrome Design exploration for the component-dx frontier (design it twice via three parallel divergent proposals, synthesized): a CustomEditor subclass that overrides only render(), wrapping the inherited output in a │-bordered box (the default Editor has no side borders at all) with runtime-state labels baked into the border corners, matching the user's sketch. projectBorderedChrome is a pure, domain-ignorant function (pre-formatted label strings in, decorated lines out) so it stays reusable for the request_* question-form pickers the user wants to refine next, not just this editor. Because a real Editor's bottom border is not reliably the last rendered line -- autocomplete dropdown rows are appended after it -- the bottom border is found by scanning backward for an Editor-shaped border line, not a fixed index. Verified against the real (non-compiled) pi-tui Editor source at ~/.pi/pi-mono/packages/tui, not just its .d.ts. Registered as an [experimental] preview-harness entry only; not wired into src/.pi/extensions/chrome/index.ts. This also forced component-preview.ts's keybindings stub to become a real pi-tui KeybindingsManager, since CustomEditor.handleInput needs .matches() for escape/ctrl+d -- a gap manual PTY smoke-testing caught that the first unit tests missed (fixed and back-filled with onEscape/onCtrlD harness tests). Smoke-tested end-to-end via a real PTY: typing, box layout, escape-dismiss, and gallery round-trip all confirmed working.
…es, clickable URL
- Rounded corners (╭╮╰╯) instead of square, matching CardComponent's
existing box style (cards.ts).
- The box is now at least 2 content rows tall even when the editor is
empty: padContentToMinimum() inserts blank rows just before the detected
bottom border (never after, where autocomplete rows may live), shifting
the tracked bottom index accordingly.
- belowLines get a 1-column left indent.
- belowLines now accept a { text, url } union alongside plain strings,
rendered as a clickable OSC 8 hyperlink via pi-tui's own hyperlink() +
getCapabilities().hyperlinks gate (the same pattern pi-tui's Markdown
renderer already uses), falling back to plain text in terminals that
don't report hyperlink support. Kept as a generic union rather than a
dedicated 'url' field so belowLines stays usable for any future bordered
component, not just this editor's sidecar URL.
Smoke-tested end-to-end via a real PTY.
…wing + thumb, preview demo
Adds a pure scroll-window primitive, sibling to projectBorderedChrome:
projectScrollViewport(content, height, keepVisible?) => { lines, offset, isThumbRow }
Converged shape after cross-checking pi-tui's own Editor/SelectList windowing
against glyph, opentui, and lazygit/gocui — all five arrive at the same model
independently. Thumb math (size/position) mirrors the gocui/glyph proportional
formula; the thumb is reported per-row so a caller can fold it into whatever
border character it already draws (gocui/lazygit's approach, rather than a
separate column.
Wired into WorkspaceDialogComponent, fixing a real (not hypothetical) gap: its
specList/sessionList option rendering had no windowing at all, so once an
option list exceeded the overlay's resolved maxHeight, pi-tui's
compositeOverlays() silently truncated it (slice(0, maxHeight), no offset, no
indicator) and arrow-down could select an option already clipped off screen.
Height is a fixed WORKSPACE_DIALOG_MAX_VISIBLE_OPTIONS constant, not
terminal-rows-derived — the component holds no TUI reference today and
Component.render(width) never receives height; SelectList's own fixed
maxVisible is the precedented fit for this shape. #selectedIndex already
existed, so selection-follow windowing needs no new persisted scroll state.
Demoed in the component-preview harness via a new workspace-dialog-scroll
registry entry (20-spec fixture, same ctx.ui.custom overlay mounting path,
no new preview lane or wrapper component needed). Confirmed live through
agent-tui in a real terminal: window follows selection past both boundaries,
thumb tracks size/position top -> middle -> bottom correctly.
Deliberately not built this slice: wheel-scroll passthrough (needs a single
owner for the terminal mouse-mode toggle) and pointer-hover hit-testing
(pi-tui has no per-render row->component ownership map at all -- an upstream
change, not a component). Both named in memory/PLAN.md's component-dx entry.
Tests: scroll-viewport.test.ts (9 direct unit cases) + 3 new
workspace-dialog.test.ts cases (windowing, selection-follow past the edge,
thumb presence/absence scoped to option rows to avoid a false match against
the brand logo's own ANSI block art).
EOF
)
…ted frontiers memory/cards/component-dx--component-preview-harness.md and memory/cards/component-dx--scroll-viewport.md are deleted (the latter was never committed -- created and reconciled within this session). Both fully exhausted: their durable rationale now lives redundantly in code (scroll-viewport.ts's docstring, workspace-dialog/component.ts's inline comments), src/.pi/components/TOPOLOGY.md, src/dev/TOPOLOGY.md, and this commit's memory/PLAN.md paragraph. Fixed the dangling "see memory/cards/..." pointers left behind (plus two pre-existing stale pointers in component-preview.ts/registry.ts that referenced a card by the wrong prefix, predating this session) in the prior commit. memory/PLAN.md's component-dx entry: "Current execution pointer" no longer names now-deleted cards; states the direct-build-and-reconcile pattern going forward. Folded in the two forward-looking facts the deleted scroll-viewport card carried so they aren't lost: wheel-scroll passthrough is buildable without forking pi-tui but needs a single owner for the terminal mouse-mode toggle before it's built; true pointer-hover hit-testing is out of scope for any brunch component (pi-tui has no per-render row->component ownership map at all -- an upstream change). elicitor-project (FE-1085) and structured-exchange-affordance (FE-1108), both done since 2026-06-30, had full frontier definitions in PLAN.md duplicating their existing Recently Completed one-liners with nothing downstream citing the extra detail. Archived both full definitions to docs/archive/PLAN_HISTORY.md under a new 2026-07-01 Sync archive section, per the file's established per-frontier archive format. Deleted the stale untracked HANDOFF.md (never committed this session): the gap it named -- no scope card for the scroll-viewport work -- was resolved twice over (a card was created, then its content fully reconciled into canonical homes).
…no pi-tui fork needed Spike question: can Brunch receive/parse real terminal mouse-wheel escape sequences through pi-tui's existing input pipeline without forking it, and is enabling mouse-tracking mode safe for components that don't know about it? Verdict: yes, confirmed empirically via a throwaway probe (deleted, .fixtures/scratch/mouse-spike/) run live under agent-tui. tui.terminal.write enables SGR mouse mode; stdin-buffer.ts's existing SGR-sequence framing delivers a wheel event byte-intact and unsplit to both addInputListener and a focused component's handleInput; an unaware Editor-based component safely ignores it today (ESC-prefixed, matches neither decodePrintableKey decode path nor the >=32 printable-insertion guard). Sub-finding: agent-tui's scroll command is a keystroke-emulation shim (sends plain ArrowDown, not a real SGR mouse event) -- had to inject the literal sequence via agent-tui type instead. Not resolved (named, not a blocker): the native-text-selection UX tradeoff of enabling mouse mode, and who owns the global enable/disable lifecycle -- both design/ownership questions for a future ln-scope, not technical unknowns. No memory/SPEC.md change -- component-dx carries no SPEC traceability for tooling/component-level work, and there was no prior assumption entry about mouse support to update.
…ds/component-dx--wheel-scroll-passthrough.md) Full scope card for the next component-dx slice after the 2026-07-01 ln-spike confirmed the mechanism works. Scopes a single, minimal tracer: - parseWheelEvent(data) -- new pure fn (.pi/components/mouse-wheel.ts) recognizing the SGR wheel-up/wheel-down shape, ignoring click/motion/ non-mouse input. - ComponentPreviewCustomOptions.wheelScroll?: boolean on the shared showComponentPreview shim (custom-ui.ts) -- opt-in per entry, mirroring the existing overlay boolean's shape. When set, owns enable/disable of SGR mouse mode for exactly that entry's open/close lifecycle and synthesizes the equivalent ArrowUp/ArrowDown keypress into the already- created component's existing handleInput -- no new component-level API. - Wired into the workspace-dialog-scroll entry as the first real consumer. Deliberately scoped as harness-only (no production wiring, no new component-level API) with two named residuals: a real-terminal smoke test (agent-tui cannot exercise a real physical wheel event -- confirmed by the prior spike) and the production lifecycle-ownership question (this slice's per-entry opt-in does not answer who owns it for a real long-lived TUI session). memory/PLAN.md's component-dx execution pointer now names this card.
…l preview
Implements memory/cards/component-dx--wheel-scroll-passthrough.md (deleted,
consumed):
- .pi/components/mouse-wheel.ts: parseWheelEvent(data) decodes the SGR
wheel-up/wheel-down shape (bit 64), rejecting motion (bit 32),
release ('m'), and non-wheel button codes (clicks, horizontal wheel).
Pure, unit-tested in isolation.
- dev/component-preview/custom-ui.ts: ComponentPreviewCustomOptions gains
an opt-in wheelScroll?: boolean, mirroring the existing overlay boolean's
shape. When set, showComponentPreview owns SGR mouse enable/disable for
exactly that entry's open/close lifecycle and rewrites a recognized wheel
event's raw bytes to the equivalent ArrowUp/ArrowDown sequence via the
addInputListener data-rewrite path -- so TUI's own dispatch delivers it to
whichever component currently has focus, rather than a hardcoded
reference to the originally-created component. No new component-level
API; WorkspaceDialogComponent is unchanged.
- registry.ts: workspace-dialog-scroll opts in ({ wheelScroll: true }); no
other entry is affected (regression-tested).
- virtual-terminal.ts: additive "writes" log for asserting DECSET
enable/disable sequences in tests; no existing test behavior changed.
Test coverage across all three tiers named in the card: parseWheelEvent
unit tests; custom-ui.test.ts proving the opt-in enable/disable lifecycle
and wheel-to-arrow-key routing generically (including a regression case
that no entry gets mouse mode by default); and a harness test driving the
*real* workspace-dialog-scroll registry entry through a VirtualTerminal,
proving wheel-down reaches the identical rendered state as an equivalent
ArrowDown keypress.
Fixed one placement issue found in review (my own scoping miss, not the
builder's): the real-entry harness test was originally added to
.pi/components/__tests__/workspace-dialog.test.ts, importing
src/dev/component-preview/registry.js -- inverting the established
dependency direction (no existing .harness.test.ts under .pi/components/
imports from src/dev/; they all construct their component directly).
Moved it to src/dev/component-preview/__tests__/workspace-dialog-scroll.harness.test.ts,
matching precedent and the .harness.test.ts naming convention.
workspace-dialog.test.ts is now byte-identical to its prior committed state.
memory/PLAN.md's component-dx entry and both TOPOLOGY.md homes (.pi/components,
dev) are reconciled, naming the residuals honestly: a manual real-terminal
(iTerm2/Kitty/Ghostty) smoke test to confirm physical wheel emission matches
the injected SGR shape proved in harness, and production lifecycle ownership
of the mouse-mode toggle (this slice's per-entry opt-in deliberately does
not answer that for a real long-lived TUI session).
Left memory/cards/orchestrator-tool-port--plan-check-tool.md and
docs/design/CUELOOP_PATTERN_LIFTOUT.md untouched -- concurrent work from
another agent in this worktree, outside this session's manifest.
Adds docs/design/STRUCTURED_EXCHANGE_ANSWERING_PATHS.md, verified against pi-coding-agent internals (ExtensionMode/bindExtensions/hasUI(), rpc-mode.ts's ExtensionUIContext), not just the exchanges/ policy prose that already existed: - ctx.hasUI/ctx.ui.custom availability is a process-boot-time fact (Brunch always binds mode: "tui") -- not a per-caller/per-connection one. - Three structurally distinct answering paths: local-TUI tool execution, Brunch's own session.submitExchangeResponse (bypasses ctx.ui entirely), and the live web-driver broker (session.answerExchange, answer/free-text only today -- a pre-existing, independently tracked gap for choice/choices/review). - Per-response-kind coverage matrix showing a column-A (local-TUI) UI swap for choice/review is safe with respect to columns B and C. Corrects an over-strong claim from earlier this session (that ctx.ui.custom's lack of RPC relay was a hard blocker for restyling choice/review) -- Brunch's real RPC surface never reaches ctx.ui at all, so it's unaffected either way. Includes a re-verification checklist and an explicit public-API-vs-internal- implementation distinction, since pi-coding-agent is tracked continuously (D67-L) and most of what's cited here is the latter. Adds one-line pointer bullets to exchanges/, rpc/, and session/'s TOPOLOGY.md files rather than duplicating the content in three places.
…rds/component-dx--rounded-box-primitive.md) Mode: slices, earned posture (the interface was already fully designed and agreed in conversation before this file was written -- consolidation, not exploration). Consolidates three independent hand-rolled bordered-box implementations (brunch-editor.ts's projectBorderedChrome, workspace-dialog/component.ts's renderFrame + border helpers, cards.ts's CardComponent) into one canonical projectRoundedBox primitive, sibling to scroll-viewport.ts. Four slices: build the primitive, then migrate each of the three existing callers with a byte-identical-output regression test as the safety net. Scoping finding worth naming: CardComponent has no test today at all (neither it nor its one consumer, alternatives.ts, covers rendered card output) -- slice 4's acceptance criteria require writing that baseline test first, not skipping the safety net because none currently exists. Explicitly out of this sequence: giving MultiChoicePickerComponent a border + scroll-viewport wiring (the actual next request_* picker work this unblocks), and restyling choice/review response kinds -- both named, not built here. memory/PLAN.md's component-dx execution pointer now names this file.
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
The builder's stop finding was correct but conflated two independently-sized
problems -- traced precisely against both cards.ts and rounded-box.ts:
1. Content-row side-border coloring (glyph+adjacent-space grouped vs bare
glyph) is a pure byte-representation difference with zero visual effect,
and rounded-box.ts's bare-glyph convention already matches 2 of 3 original
implementations (brunch-editor, workspace-dialog), proven byte-identical
in slices 2/3. No primitive change needed -- slice 4b re-baselines
cards.test.ts to the already-canonical convention, named explicitly.
2. Top-border label alignment (cards.ts left-aligns its title; the primitive
right-aligns, matching brunch-editor) is a real, visible difference and a
genuine scoping gap -- the original feature-coverage matrix incorrectly
recorded "label in top border" as one shared feature without checking
alignment. This does need a narrow, targeted primitive widening.
Un-supersedes the card, splits slice 4 into:
- 4a: RoundedBoxOptions gains labelAlign?: 'left'|'right' (default 'right',
non-breaking); border-line-with-label construction switches from
per-glyph color scanning to two contiguous colored runs -- simpler than
the current scanner, not more complex, and matches what all three
original implementations actually did.
- 4b: migrate cards.ts against the widened primitive, re-baselining only
the content-row coloring (named in the test/commit, not silent).
No code changed this commit -- scope only. Status flipped back to active.
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Not part of the ln-* skill family (check:skills validates 19 ln-* skills, unaffected by this) and not referenced anywhere else in the repo (docs/praxis/ ln-skills.md never listed it). Per user instruction: no longer necessary -- planning-doc edits stay on the active feature branch by default, which was already this skill's own recommended default in the common case; a dedicated advisory skill for the rare separate-planning-PR case wasn't earning its keep. .claude/skills/ is a directory symlink to .agents/skills/, so this single deletion removes it from both paths.
component-dx (FE-1115) is paused: the preview harness plus three shared
presentation primitives (projectScrollViewport, mouse-wheel.ts's wheel
decoder, projectRoundedBox) all shipped as either preview-harness-only work
or behavior-preserving refactors of an already-shipped component -- zero
production UX change throughout. Wiring BrunchEditorComponent into real
production surfaces and rebuilding the request_* response components is a
different risk category (the first genuine production UX change either
primitive will drive), so it splits into its own frontier rather than
continuing as more component-dx slices.
New frontier: bordered-chrome-production (Certainty: proving; no Linear
issue/branch yet). Its Objective deliberately separates three threads rather
than bundling them under one editor-installation framing, correcting an
implicit conflation from earlier in this session:
1. Main editor: wire BrunchEditorComponent via ctx.ui.setEditorComponent
(persistent input editor, D22-L/D35-L chrome territory) -- carries a
real, unverified assumption about the editor region's height budget.
2. answer (free-text): give request_response's collectAnswerFromSources
path a ctx.ui.custom-backed bordered component -- independent of #1;
ctx.ui.setEditorComponent and ctx.ui.editor(...) are structurally
distinct mechanisms in pi-coding-agent (confirmed against source), so
wiring the main editor does not touch request_response's answer dialog
at all.
3. choice/review: same ctx.ui.custom-backed treatment for
collectChoiceFromUi/collectReviewFromUi -- most ready-to-build of the
three per docs/design/STRUCTURED_EXCHANGE_ANSWERING_PATHS.md's coverage
matrix (already proven not to regress session.submitExchangeResponse).
Also trimmed memory/PLAN.md's Recently Completed list from 8 to 3 entries
(elicitation-gap-guidance plus the two component-dx completions directly
relevant to this split), archiving the other 7 one-liners to
docs/archive/PLAN_HISTORY.md per ln-plan's own step-7 maintenance procedure --
mechanical move, no re-summarization needed since the one-liners were already
well-formed.
Verified: check:skills OK (19 ln-* skills), check:markdown-links clean.
5196d5d to
5846c21
Compare
e293ee7 to
40ece5c
Compare
Merge activity
|
ln-sync at arc close: record the completed D101-L port cutover in SPEC (D101-L row, I56-L rewrite, requirement 26, ExecutionPorts lexicon row, worker content-quality blind spot), thin the five D101-L refinement blockquotes to src/executor/TOPOLOGY.md pointers, archive done frontier definitions to PLAN_HISTORY, close the orchestrator-cutover arc in PLAN, acknowledge in-flight FE-1116/FE-1115 (PRs #280/#281), reconcile executor topology headers, fix the stale I52-L comment to I56-L, and delete 11 exhausted scope cards.
ln-sync at arc close: record the completed D101-L port cutover in SPEC (D101-L row, I56-L rewrite, requirement 26, ExecutionPorts lexicon row, worker content-quality blind spot), thin the five D101-L refinement blockquotes to src/executor/TOPOLOGY.md pointers, archive done frontier definitions to PLAN_HISTORY, close the orchestrator-cutover arc in PLAN, acknowledge in-flight FE-1116/FE-1115 (PRs #280/#281), reconcile executor topology headers, fix the stale I52-L comment to I56-L, and delete 11 exhausted scope cards.

Stack Context
This branch sits above the elicitation-gap model change and provides the Pi TUI component iteration surface used by later exchange/chrome work.
What changed
npm run dev:componentsand a faithful preview harness for.pi/componentspresentation lanes.Why
The later production UI work needs a fast, real-terminal-adjacent way to inspect Brunch-owned components without booting a workspace/session path for every visual change.
Verification