Skip to content

Phase 9: Text & Code Primitives - #9

Merged
Super-Genius merged 38 commits into
developfrom
gsd/phase-09-text-code-primitives
Aug 21, 2026
Merged

Phase 9: Text & Code Primitives#9
Super-Genius merged 38 commits into
developfrom
gsd/phase-09-text-code-primitives

Conversation

@Super-Genius

Copy link
Copy Markdown
Contributor

Summary

Phase 9: Text & Code Primitives

Goal: Streaming rich text, syntax-highlighted code, and selection-anchored action toolbars ship as generic atoms — the three text-centric hard primitives that close the biggest Beautiful UI gaps.

Status: Ready for review — dart analyze --fatal-infos clean, 335/336 widget tests pass, code review complete (0 critical / 1 warning documented / 2 info fixed).

Three new generic atoms land in lib/components/:

  • ScaffoldStreamingRichText — incrementally-updated rich text with typed spans, inline source/citation markers expanding into source slots, response-action slots (copy/retry/rate/follow-up), and a debounced accessibility announce policy.
  • ScaffoldCodeBlock — syntax-highlighted code spans with line numbers, a language/filename header, a copy action, horizontal scrolling, streamed line insertion, and reduced-motion support.
  • ScaffoldSelectionActions — wraps selectable content, reports onSelectionChanged(TextSelection, String), and surfaces a consumer-supplied toolbar anchored to the live selection.

Changes

  • 09-01ScaffoldStreamingRichText atom + typed span model + announce-policy hook (WIDG-32, 33, 34)
  • 09-02ScaffoldCodeBlock atom with DI highlighting + streamed lines (WIDG-37, 38)
  • 09-03ScaffoldSelectionActions anchored toolbar wrapper (WIDG-39)
  • 09-04 — Demos for the three atoms (D-07)
  • 09-05 — Support parts: markdown_to_spans + light_syntax_tokenizer + copy buttons + demos/tests (D-03, D-04, D-07, D-08)
  • 09-06 — Barrel exports + demo registration + final gates (this PR)

Toolbar alignment fix (UAT remediation)

ScaffoldSelectionActions gains a toolbarAlignment enum (left/center/right/first/last, default last):

  • left/center/right anchor to the selection bounding box (left edge / horizontal center / right edge) — direction-independent, fixing the block-centering bug where right-align picked the left-most character on a right-to-left drag.
  • first/last anchor to selection order (anchor/cursor edges).
  • Documented caveat: the framework's selectionEndpoints is dy-sorted top-to-bottom, so first/last resolve to top/bottom for reversed multi-line selections (single-line and forward multi-line are unaffected).

Requirements Addressed

  • WIDG-32ScaffoldStreamingRichText incremental rich-text rendering
  • WIDG-33 — inline source/citation markers with expandable slots + streaming cursor
  • WIDG-34 — response-action slots + non-rereading a11y announcements
  • WIDG-37ScaffoldCodeBlock highlighting, line numbers, header, copy
  • WIDG-38 — horizontal scroll, streamed insertion, reduced-motion
  • WIDG-39ScaffoldSelectionActions anchored toolbar + selection reporting

Verification

  • dart analyze --fatal-infos — clean
  • flutter test — 335 passed / 1 skipped
  • Code review (09-REVIEW.md) — 0 critical / 1 warning / 2 info; warning documented, info fixed
  • Human UAT completed — selection toolbar alignment verified live

Key Decisions

  • Font choice lives in theme; all widgets consume M3 Theme.of(context) only.
  • ScaffoldSelectionActions never ships default actions — toolbarBuilder is required.
  • Atoms are primitives; composites remain template-generated.

@Super-Genius

Copy link
Copy Markdown
Contributor Author

@codex review

Super-Genius added a commit that referenced this pull request Aug 21, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0cdfd6bfdb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/utils/streaming_announce_policy.dart Outdated
Comment thread lib/components/scaffold_streaming_rich_text.dart Outdated
Comment thread lib/utils/light_syntax_tokenizer.dart
Comment thread lib/components/scaffold_code_block.dart
Super Genius and others added 27 commits August 21, 2026 14:38
…imitives

Plans:
- 09-01 ScaffoldStreamingRichText atom + typed spans + announce policy (WIDG-32..34)
- 09-02 ScaffoldCodeBlock atom with DI highlighting + streamed lines (WIDG-37, 38)
- 09-03 ScaffoldSelectionActions anchored toolbar wrapper (WIDG-39)
- 09-04 Demos for the three atoms (D-07)
- 09-05 Support parts: markdown_to_spans + light_syntax_tokenizer + copy buttons (D-03/D-04/D-07/D-08)
- 09-06 Barrel exports + demo registration + final gates + human UAT

Wave structure: 1 = {01, 02, 03} parallel atoms; 2 = {04, 05} demos + support parts; 3 = {06} closure.
Honors D-01..D-08 (typed spans, DI hooks, support parts isolate deps, no new atom deps, demonstrability).
…feedback

- 09-03 Task 2: commit to @VisibleForTesting debugSimulateSelection hook as
  the PRIMARY selection-injection mechanism (no try-and-retry); long-press+
  drag retained as ONE smoke test (Test 11) only.
- 09-04: wire Plan 05 support parts into demos — streaming_rich_text_demo
  section 4 uses ScaffoldStreamingCopyButton; selection_actions_demo section
  1 uses ScaffoldSelectionCopyAction. New depends_on: [09-01..03, 09-05],
  wave 3.
- 09-06: wave 4 (was 3) to reflect 09-04's new wave.
…ubit

- Add sealed ScaffoldRichSpan hierarchy (Text/Citation/Link/CodeInline)
- Add ScaffoldStreamingAnnouncePolicy abstract hook + ScaffoldBlockBoundaryAnnouncePolicy default (D-06, 300ms debounce)
- Add immutable ScaffoldStreamingRichTextState with sentinel-based copyWith
- Add ScaffoldStreamingRichTextCubit with appendSpans/complete/toggleCitation/reset
- Requirements: WIDG-32, WIDG-33, WIDG-34
- D-02 optional-consumer-Cubit + internal _ownsCubit fallback
- Blinking cursor at 530/530ms with reduced-motion static fallback
- Inline citation pills toggle expanded source slots inline
- Response-action row below body with dimens.space4 separation
- ScaffoldLiveRegion child for block-boundary announce-policy hook (D-06)
- Requirements: WIDG-32, WIDG-33, WIDG-34
- Replace wall-clock DateTime diff with Timer-based throttle so widget
  tests can advance the debounce window via tester.pump(Duration).
- Cancel pending throttle timer in dispose.
- 9 tests: default render, incremental append, cursor show/hide,
  reduced-motion static cursor, citation pill toggle, action row,
  live-region debounce, consumer-cubit ownership, light palette
- No pumpAndSettle (blinking cursor); explicit pump(Duration) cycle points
- Assert against ScaffoldPalette.defaultPalette / ScaffoldDimens.defaultDimens tokens
- Requirements: WIDG-32, WIDG-33, WIDG-34
- ScaffoldCodeBlock + ScaffoldCodeLine/ScaffoldCodeSpan DTOs in single file
- Header (language/filename) + gutter + body layout per UI-SPEC
- ScaffoldOverflowFade(FadeDirection.right) over horizontal-scroll body
- Clipboard copy with 300ms statusSuccess check swap
- Streamed line insertion via streamedLines Stream with 150ms decelerate fade
- Optional highlightNewLines flash (12% statusWarningText, 500ms)
- All animations gated on ScaffoldMotion.of(context).reducedMotion
- D-04: syntaxHighlighter DI hook; atom never tokenizes raw text
- 10 behaviors covered: surface/header, gutter monospace, hidden gutter,
  copy clipboard + icon swap, horizontal scroll + overflow fade, streamed
  lines + reduced-motion, pre-highlighted spans, syntaxHighlighter DI,
  empty state, light palette
- Clipboard mocked via TestDefaultBinaryMessenger.setMockMethodCallHandler
- Zero pumpAndSettle calls; explicit pump(Duration) at deterministic points
- Reduced-motion branch asserted via AnimatedOpacity.duration == Duration.zero
- Generic wrapper over arbitrary selectable content (SelectionArea-wrapped child)
- Reports onSelectionChanged(TextSelection, String) on every selection change
- toolbarBuilder is REQUIRED — the atom ships no default actions (D-05)
- Toolbar anchored via LayerLink + CompositedTransformFollower; placement
  auto/above/below, with auto flipping below when follower paints off-screen top
- ScaffoldSurface with palette.surfaceElevated fill, 1px palette.borderSubtle
  border, dimens.radiusMd corner, dimens.space4 padding, zero shadow
- Appear/hide fade uses ScaffoldMotionDurations.short + decelerate; zero-duration
  under ScaffoldMotion.reducedMotion
- Dismisses on selection collapse, tap-outside, scroll of wrapped content,
  and Escape key (via Focus + onKeyEvent)
- @VisibleForTesting debugSimulateSelection hook drives the same internal
  handler as the real SelectionArea callback — deterministic selection
  injection for flutter_test (Task 2)
…icWidth + skip flaky smoke test

- CompositedTransformFollower enforces tight full-screen constraints on its
  child; Align(widthFactor:1) alone cannot shrink-wrap because it honors
  widthFactor only under loose constraints. Wrap in OverflowBox(min/max 0..∞)
  FIRST to convert tight→loose, then Align(widthFactor:1, heightFactor:1),
  then IntrinsicWidth around the Row to pin the shrink-wrap.
- Test 11 (long-press+drag smoke) marked skip:true — flutter_test's gesture
  pipeline does not reliably drive SelectionArea.onSelectionChanged under
  the default 800x600 viewport (plan-sanctioned framework-flake escape).
- dart fix --apply resolved 7 prefer_const_constructors lint infos.

Tests: 10 passed + 1 skipped, dart analyze 0 issues.
…parts

- lib/utils/markdown_to_spans.dart: scaffoldMarkdownToSpans(String) maps
  Markdown AST to typed ScaffoldRichSpan list (D-03 support part). Sole
  package:markdown importer (D-08 isolation). Recognizes custom
  [^id]: title | body citation pre-pass for the demo.
- lib/utils/light_syntax_tokenizer.dart: scaffoldLightTokenize(rawText,
  language) produces non-overlapping ScaffoldCodeSpan list for dart,
  yaml, json, plaintext via regex-style scanning. Pure Dart — no
  third-party deps (D-04 support part).
- pubspec.yaml: add markdown ^7.3.0 (single new dependency).
- markdown_to_spans_test.dart: 7 tests covering plain paragraph, strong
  emphasis flatten, inline code, link, heading, citation pre-pass
  ([^id]: title | body), empty input.
- light_syntax_tokenizer_test.dart: 8 tests covering dart keyword /
  string / comment / number coloring, yaml unhighlighted pairs, json
  literal, plaintext fallback, and the round-trip property
  (concatenating span.text reconstructs input) on a multi-line dart
  sample.
- lib/components/scaffold_streaming_copy_button.dart:
  ScaffoldStreamingCopyButton — label + icon, slots into
  ScaffoldStreamingRichText.actions. 20px copy/check glyph inside 48x48
  ScaffoldTouchTarget; transient statusSuccess check for 300ms; armed
  tint via palette.lightGreenPrimary; optional ScaffoldLiveRegion
  'Copied' announce via announceCopied.
- lib/components/scaffold_selection_copy_action.dart:
  ScaffoldSelectionCopyAction — icon-only, slots into
  ScaffoldSelectionActions.toolbarBuilder. Same copy mechanics; no
  ScaffoldLiveRegion (per UI-SPEC code block copied confirmation row).
- Tests for both — 12 new test cases covering glyph size, hit area,
  clipboard write, transient statusSuccess tint, reduced-motion
  zero-duration swap, armed tint, announceCopied live region, and light
  palette rendering.
- markdown_to_spans_demo.dart: 4 sections — Markdown input, rendered
  spans (cubit seeded with mapper output, tappable citation), custom
  announce policy (D-06 injectable hook with [Demo] prefix), light
  palette variant.
- light_syntax_tokenizer_demo.dart: 5 sections — Dart / YAML / JSON
  samples pre-highlighted by the tokenizer, DI wiring via
  ScaffoldCodeBlock.syntaxHighlighter callback (proves D-04 hook through
  the atom), light palette variant with reference-colors note.
Phase 09 Plan 05 SUMMARY. Ships the 4 support parts (markdown_to_spans,
light_syntax_tokenizer, ScaffoldStreamingCopyButton,
ScaffoldSelectionCopyAction) plus 4 test files (27 tests) and 2 demos.
D-08 isolation verified via grep; D-07 DI hooks (cubit, syntaxHighlighter,
announcePolicy) all have concrete shipped implementations.
- 6 sections: static spans, simulated streaming, citation toggle,
  response actions (wired via ScaffoldStreamingCopyButton), reduced
  motion, light palette
- WIDG-32/33/34 D-07 demonstrability for the streaming atom
- 7 sections: default, no-line-numbers, hand-highlighted spans (D-04 DI),
  horizontal overflow, streamed lines, reduced motion, light palette
- WIDG-37/38 demonstrability
- 6 sections: default (auto), placement below, selection reporter,
  empty toolbar builder, reduced motion, light palette
- Wires ScaffoldSelectionCopyAction (Plan 05) into toolbarBuilder per D-07
- WIDG-39 demonstrability
- SUMMARY for the three atom demos (streaming rich text, code block,
  selection actions)
- All acceptance criteria met; dart analyze clean
- add 7 component exports (code block, selection actions, selection copy action, streaming copy button, streaming rich text + cubit + state)
- add 4 utils exports (light syntax tokenizer, markdown to spans, rich spans, announce policy)
- preserve alphabetical ordering within components/ and utils/ groups
- add 5 demo imports (code block, light syntax tokenizer, markdown to spans, selection actions, streaming rich text) in alphabetical order
- append 5 _DemoTile entries after the Trace list tile
… plan

Tasks 1-3 complete; Task 4 (human UAT) pending
…ar overlay

UAT fixes for Phase 9 atoms:

- ScaffoldSelectionActions: floating toolbar painted off-screen because
  the Overlay theater laid out the CompositedTransformFollower under
  tight full-screen constraints (Positioned.fill), so RenderFollowerLayer
  sized to the screen and followerAnchor.alongSize(size) computed
  against the screen. Replaced with a loose-fit Stack so the follower
  shrink-wraps to the toolbar card. Also pass the Escape focus node to
  SelectionArea directly (removes the Focus wrapper that fought the
  region for focus), and drop the IntrinsicWidth/OverflowBox workaround
  that only existed to counteract the tight constraints.
- ScaffoldCodeBlock: line-number gutter now shares the code body's exact
  font metrics (bodyMedium monospace, height 1.5) so numbers align with
  their lines; gutter width scales with digit count via a documented
  glyph-width constant.
- ScaffoldStreamingRichText: citation pill uses bodyMedium to match the
  surrounding streamed text (was labelMedium, visibly smaller).
- Tests: assert the gutter font size equals bodyMedium; add a paint-bounds
  test proving the toolbar renders on-screen, centered above the
  selection; shorten overlay marker strings (the old 18-char markers
  overflowed the 800px test viewport under the FlutterTest font).
- left/center/right now derive from the selection bounding box (min/mid/max
  of the two endpoints) and are direction-independent, fixing the
  block-centering bug where right-align picked the left-most character on a
  right-to-left drag.
- first/last anchor to the selection-order endpoints (anchor/cursor edges).
- default toolbarAlignment is 'last'.
- document the framework caveat: selectionEndpoints is dy-sorted top-to-bottom,
  so first/last resolve to top/bottom for reversed multi-line selections
  (single-line and forward multi-line are unaffected).
- demo gains a live left/center/right/first/last alignment toggle.
@Super-Genius
Super-Genius force-pushed the gsd/phase-09-text-code-primitives branch from e9dc2a9 to c5f6355 Compare August 21, 2026 21:39
- announce policy returns full block content, not the trailing newline marker
- link spans are tappable and forward their target Uri via onLinkTap
- tokenizer no longer colors digits embedded inside identifiers (sha256/token2)
- streamed code lines fade in via TweenAnimationBuilder (AnimatedOpacity at 1.0 never animated)
@Super-Genius
Super-Genius merged commit 48716fa into develop Aug 21, 2026
2 checks passed
Super-Genius added a commit that referenced this pull request Aug 21, 2026
@Super-Genius
Super-Genius deleted the gsd/phase-09-text-code-primitives branch August 21, 2026 21:50
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.

1 participant