Skip to content

sparkles:ui migration — Feature Requirements (MIG)

Status: partial · Date: 2026-08-05 · Scope: consolidating the repository's two parallel UI stacks into sparkles:ui, and porting apps/hue onto it — the sequencing, the compatibility rules, and the milestone plan.

Design & rationale

Two things must happen, and they are independent of each other only in principle:

  1. sparkles:core-cli currently contains a second UI stack — box, table, tree, meter, progress, task list, live region, header, layout helpers, hyperlinks and a terminal theme. It is mature and widely used, but it is a parallel vocabulary: its theme has no relationship to the toolkit's slots, it has its own BorderStyle enum meaning something different, and it uses a grapheme-correct width authority the toolkit does not. It belongs in sparkles:ui, restructured into view models and views; core-cli should be about command-line arguments.

  2. apps/hue implements ~30 visual components, of which six use the toolkit. The rest are written once per backend. It is the toolkit's first real consumer and its port is what proves the design.

The constraint throughout is that the repository stays green: the CI helper, the release tool and the test runner all render terminal UI today and must keep working at every commit.

Consolidation (MIG1MIG7)

IDRequirementStatusTraces to
MIG1Terminal capability probing moves to sparkles:base, and the terminal's cell-geometry types move to sparkles:tui, so no UI package depends on core-cli for either.full (31fb39c5)base.term_caps; tui.geometry
MIG2core-cli's UI components move into sparkles:ui and are restructured as presentation-free view models plus widget views — in three separate steps, not one (see the phase note below).partial (2a; 2b started)widgets.md VMD
MIG3Existing consumers must keep working at every step. The adapter direction flips mid-migration: string emitters stay canonical while they are the only implementation, and become thin adapters over the widget path only once that path exists.not startedadapter shims
MIG4core-cli's help output must be expressed as widgets, so the last UI concern leaves the package and help rendering gains the same theming and capability gating as everything else.not startedcore_cli.help_formatting
MIG5The toolkit must adopt the repository's grapheme-correct width authority rather than its own codepoint count, resolving the current disagreement between the layout pass and the cell backend.not startedLAY5
MIG6The test runner's use of the moved components must stay introspection-guarded. base/core-cli/test-utils source-include the runner rather than depending on it, and in those builds the toolkit is absent — so it detects the components with __traits(compiles, …) and degrades. The move retargets those guards; it must not make the imports unconditional.full (M3a)test_runner.reporting hasUiComponents
MIG7sparkles:core-cli is a published package, so the move is a breaking change for external consumers. Compatibility shims under the old module names are not possible (see below); the break is instead documented in the changelog and the module mapping published.full (M3a)docs/specs/ui/migration.md; changelog

Why sparkles.core_cli.ui.* cannot be a compatibility shim

The obvious kindness — leave sparkles.core_cli.ui.box behind as a public import sparkles.ui.components.boxdoes not compile, and the reason is worth recording because it is not obvious.

Keeping that shim means sparkles:core-cli hosts a package named sparkles.core_cli.ui _while its own modules import sparkles.ui._* (prompts needs the theme and the live region). From inside package sparkles.core_cli, the name sparkles.ui.components.themethen resolves through the nearersparkles.core_cli.ui, and the compiler reports the symbols as missing:

prompts.d: Error: module `sparkles.ui.components.theme` import `Semantic` not found

It reproduces with a plain selective import and with static import alike, and it disappears the moment the shim package is removed. The collision is between the package names, so no import style avoids it: a package cannot both shadow sparkles.ui and depend on it.

Since core-cli must import the toolkit (PKG4 keeps prompts and help there), the shims lose. External consumers get a documented break and a module mapping — sparkles.core_cli.ui.Xsparkles.ui.components.X — which is a mechanical find-and-replace.

The three phases of MIG2, and why they cannot be one

The tempting shape — "move the components and make the old string functions thin adapters over the new widget path" — is inverted, because at the moment of the move there is no widget path to adapt to.

The existing emitters are ANSI-transparent string producers: they accept pre-styled text, measure it with the grapheme-correct visible width, preserve colors and hyperlinks across wrapped rows, and reset style before a frame. To express that as widgets requires styled runs within a node (WGT6), a link concept (WGT21), the track sizer (LAY9) and width-aware measurement with wrapping (LAY4/LAY5) — all of which land later.

So MIG2 is three steps, each independently green:

PhaseScopeCanonical implementation
2aMechanical move — modules relocate, imports rewrite, imports rewrite. Output must be byte-identical; goldens unchanged.the string emitters
2bView-model extraction — the presentation-free half (grid model, column widths, tree flattening, meter fill) is separated out and tested with no renderer. Emitters call into it.the string emitters
2cWidget views — added on top once the layout and widget capabilities exist; the emitters become adapters that render a widget tree through the cell backend.the widget tree

Phase 2a must not attempt any restructuring: a move that also changes behavior has no reliable oracle, because the goldens are the oracle.

hue port (MIG8MIG12)

IDRequirementStatusTraces to
MIG8hue's per-backend rendering must be replaced by one widget tree per screen. hue retains only argument parsing, document loading, the syntax pipeline, input handling and its views.partial (dd2b4370) — markdown, twoslash, raw source and explorer views are widget trees; app-owned chrome paint remains HUE-O2hue UI architecture
MIG9hue's frame-loop state must become a single owned view-state value with the interaction state machines this toolkit provides, replacing peer locals and mutating closures.partial (b4b837f4) — ViewerModel owns the document pipeline, but panes, input, flashes, hover, resize and selection still live beside it with captured closures (HUE-O1)principles.md PRN1, PRN7
MIG10hue's document model must become composable content kinds rendered by re-entrant views, so nested content (a document embedding a richer block, a popup embedding a document) reuses one code path instead of duplicating it.full (bc3e1f17) — one Document/pipeline, re-entrant viewMarkdownInto, viewTwoslashDocument, twoslash JSDoc through the markdown view, the explorer's preview pane is the viewerwidgets.md WGT2
MIG11Each ported component must delete its per-backend predecessors in the same change, so the duplication count strictly decreases and no third copy is created.full (155ce512) — every swap deleted its predecessor: preview_ansi, previewer, the plines flattener (PreviewItem), runGuiTwoslash, the TUI hand rendererMIG8
MIG12Behavior differences between backends that exist only because the implementations were separate must be resolved to one behavior, not preserved.full — one thumb formula, one selection/copy model, one copy affordance per content kind, one fold behavior, one window loopprinciples.md PRN8

Sequencing

Ordered by dependency; each step is independently green.

StepScopeRequirements
0Capture per-backend goldens — the oracle every later step is checked againstTGT10
1Package graph: capability probing and cell geometry relocateMIG1, PKG3
2Cycle-safe dub integration; ui becomes a libraryPKG5, PKG6
3Theme unification — syntax, slot and metric channels; the glyph channel is declared here and filled in 4aTHM6
4acore-cli UI components move mechanically; byte-identical output; deprecation shimsMIG2, MIG3, MIG6, MIG7
4bView-model extraction from the moved componentsMIG2, VMD
5Layout capabilities the components needLAY4LAY10
6Input package and hit identityINP
7Widget model hardening and the component catalogWGT, STM
4cWidget views for the moved components; the string emitters become adaptersMIG2, MIG3, MIG4
8Backend adapter packages; HTML as a first-class targetTGT4, TGT6
9hue: composition core, then chrome, then content, then tree and foldingMIG8MIG12

IMPORTANT

Step 4c is deliberately out of numeric order. The widget views for the moved components cannot be written until steps 5–7 supply the layout and widget capabilities they need. Steps 4a and 4b land early because they are mechanical and unblock everything else; 4c waits. This is MIG3's adapter-direction flip made concrete.

Step 0 precedes the theme work, not just the layout work: theme unification and the component move can both shift rendered output, so the goldens must predate both.

Compatibility & verification

  • Parity harness first. Capture per-backend goldens at step 0 — before the theme work, not just before the layout work — and diff after every step; it is the primary safety net.
  • Lockstep tests guard the theme consolidation — any palette or metric drift fails the build rather than silently changing appearance.
  • Runnable documentation examples cover the moved components and must be re-verified, along with their documentation pages.
  • Headless rendering through the recording canvas keeps every step testable without a window or a terminal.
  • The no-GPU build configuration must keep building at every commit.

Module coverage

This spec governs moves rather than a module set; per-module ownership after each move is recorded in the destination spec's coverage table.

Relationship to existing specs

PieceRole
feature-requirements.md PKGthe target package graph this migration reaches
hue feature specthe consumer being ported, and its requirement inventory
hue UI architecturehue's own consumption requirements after this spec supersedes its library-level ones
AGENTS.mdpackage table and test-runner integration recipes to update

Overview · Feature requirements · Principles