Skip to content

FE-1115: Refine Pi TUI component DX: preview harness, component refinements, and new components#281

Merged
lunelson merged 25 commits into
nextfrom
ln/fe-1115-component-preview-dx
Jul 3, 2026
Merged

FE-1115: Refine Pi TUI component DX: preview harness, component refinements, and new components#281
lunelson merged 25 commits into
nextfrom
ln/fe-1115-component-preview-dx

Conversation

@lunelson

@lunelson lunelson commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

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

  • Added npm run dev:components and a faithful preview harness for .pi/components presentation lanes.
  • Retired the unwired TUI lab extension while keeping its render-only component as a preview reference.
  • Added and exercised shared presentation primitives: rounded editor chrome, scroll viewport, and harness-only wheel-scroll passthrough.
  • Reconciled component/dev topology docs and retired consumed component-DX cards.

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

  • Component preview unit/harness coverage added with the branch
  • Real PTY smoke checks were run for the preview entries named in the commits

@lunelson lunelson changed the base branch from next to graphite-base/281 July 1, 2026 16:21
@lunelson lunelson force-pushed the ln/fe-1115-component-preview-dx branch from b6adbbb to e293ee7 Compare July 1, 2026 16:21
@lunelson lunelson changed the base branch from graphite-base/281 to ln/fe-1116-elicitation-gap-guidance July 1, 2026 16:21
@lunelson lunelson changed the base branch from ln/fe-1116-elicitation-gap-guidance to graphite-base/281 July 1, 2026 16:21
@lunelson lunelson marked this pull request as ready for review July 1, 2026 16:28
Copilot AI review requested due to automatic review settings July 1, 2026 16:28
@cursor

cursor Bot commented Jul 1, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Mostly dev tooling and presentation refactors, but workspace-dialog scroll/windowing changes real startup picker behavior for long spec lists; production chrome wiring is explicitly deferred.

Overview
Adds npm run dev:components and a real-terminal gallery (src/dev/component-preview/*) so .pi/components can be exercised without a workspace/session/DB. Entries mirror each component’s production ctx.ui.custom contract (overlay vs inline swap), plus static lanes for message renderers and chrome; optional SGR wheel→arrow translation is harness-only.

Introduces shared layout primitives — projectRoundedBox, projectScrollViewport, and experimental BrunchEditorComponent / projectBorderedChrome — and refactors CardComponent and the workspace dialog to use them. The dialog now windows long spec lists (fixed visible option cap + border thumb), which is the main shipped UX change beyond dev tooling.

Retires the unwired tui-lab Pi extension (style lab lives as a preview-only component under .pi/components/tui-lab/). Adds docs/design/STRUCTURED_EXCHANGE_ANSWERING_PATHS.md and updates PLAN/topology docs to pause component-dx and queue bordered-chrome-production for production chrome wiring.

Reviewed by Cursor Bugbot for commit 40ece5c. Bugbot is set up for automated code reviews on this repo. Configure here.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:components harness (gallery + per-entry registry) with a real Theme and KeybindingsManager, plus dev-facing docs/topology updates.
  • Adds shared presentation primitives in .pi/components/ (projectRoundedBox, projectScrollViewport, parseWheelEvent) and refactors cards + workspace-dialog to use them.
  • Retires the unwired tui-lab extension registrar while keeping TuiStyleLabComponent as 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));
Comment on lines +1 to +2
import type { KeybindingsManager, Theme } from '@earendil-works/pi-coding-agent';
import { type Component, type TUI } from '@earendil-works/pi-tui';
Comment thread src/dev/TOPOLOGY.md
Comment on lines +39 to +42
`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.
Comment on lines +60 to +63
tui.start();
await entry.open(tui, theme, keybindings);
tui.stop();
return;
lunelson added 13 commits July 3, 2026 12:20
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.
lunelson and others added 12 commits July 3, 2026 12:20
…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.
@lunelson lunelson force-pushed the graphite-base/281 branch from 5196d5d to 5846c21 Compare July 3, 2026 12:20
@lunelson lunelson force-pushed the ln/fe-1115-component-preview-dx branch from e293ee7 to 40ece5c Compare July 3, 2026 12:20
@graphite-app graphite-app Bot changed the base branch from graphite-base/281 to next July 3, 2026 12:21
@graphite-app

graphite-app Bot commented Jul 3, 2026

Copy link
Copy Markdown

Merge activity

  • Jul 3, 12:21 PM UTC: Graphite rebased this pull request, because this pull request is set to merge when ready.

@lunelson lunelson merged commit 9aa1209 into next Jul 3, 2026
12 checks passed
@lunelson lunelson deleted the ln/fe-1115-component-preview-dx branch July 3, 2026 12:21
kostandinang added a commit that referenced this pull request Jul 3, 2026
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.
kostandinang added a commit that referenced this pull request Jul 3, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants