Skip to content

Phase 11: Verification & Coverage Gate (v1.2 milestone) - #11

Merged
Super-Genius merged 37 commits into
developfrom
gsd/phase-11-verification-coverage-gate
Aug 24, 2026
Merged

Phase 11: Verification & Coverage Gate (v1.2 milestone)#11
Super-Genius merged 37 commits into
developfrom
gsd/phase-11-verification-coverage-gate

Conversation

@Super-Genius

Copy link
Copy Markdown
Contributor

Summary

Phase 11: Verification & Coverage Gate
Goal: Every v1.2 atom meets the v1.1 shipping bar (tests, demo, barrel export) and the 19-component Beautiful UI set is demonstrably composable from shipped scaffold atoms.
Status: Verified ✓ (3/3 must-haves passed, 2026-08-23)

Phase 11 closed out the v1.2 milestone: the test suite went zero-skip, a reproducible dual-theme image-capture harness now generates the 26-demo README gallery from example/, and the README was restructured with corrected counts, a 26-entry component gallery, and the 19-row WIDG-45 coverage proof. Post-verification fixes include a capture-harness color-fidelity fix (dark captures were painting the light-scheme primary on dark surfaces) and palette text-color alignment in remaining demo/component text.

Changes

Plan 11-01: Skip-Test Deletion + Zero-Skip Sweep Gate

Deleted the permanently-skipped long-press SelectionArea smoke test; sweep gate evidence (analyzer clean, zero skips, barrel exports) recorded.

Key files: test/components/scaffold_selection_actions_test.dart

Plan 11-02: Demo Image Capture Harness

Reproducible WRITER harness (capture_images_test.dart) rasterizes all 26 demos under dark+light themes via RenderRepaintBoundary.toImage + tester.runAsync, with real Roboto fonts, into images/ (52 PNGs).

Key files: example/test/capture_images_test.dart, images/*_dark.png, images/*_light.png

Plan 11-03: README Restructure

Corrected counts (454 tests, 26 demos), 26-entry Component gallery, 19-row WIDG-45 Coverage table, images/ added to the layout tree.

Key files: README.md

Post-verification fixes

  • fix(capture-images): themeAnimationDuration: Duration.zero — interleaved tester.runAsync froze in-flight AnimatedTheme tickers, leaving every _dark capture with a plain ElevatedButton painting the light-scheme primary (#1C6B50) over dark surfaces instead of the app's dark primary (#8BD5B5). All 52 images regenerated and pixel-verified.
  • fix(theme): demo/example text and DesktopBodyContainer/Loading/StringButton now resolve ScaffoldPalette text colors; example _buildTheme aligns ColorScheme onX slots with the palette.
  • Code-review findings WR-01..WR-05 resolved (selection-actions rebuild/escape-focus, disclosure AnimatedSize stability, capture-harness font path + awaited write).

Requirements Addressed

  • WIDG-44 — each new atom ships with widget tests, a demo in example/, and barrel export (same bar as v1.1)
  • WIDG-45 — all 19 Beautiful UI components demonstrably composable from shipped scaffold atoms (7 ready + 8 thin + 4 primitive-enabled)

Verification

  • Automated verification: passed (11-VERIFICATION.md, 3/3 must-haves)
  • dart analyze --fatal-infos clean
  • Full flutter test — 454 tests, all passing
  • Regenerated images pixel-verified: dark captures paint dark primary #8BD5B5, light captures unchanged

Key Decisions

  • D-06 (11-01): deleted the permanently-skipped smoke test rather than unskip-and-fix
  • Captures are written by a WRITER harness — no golden pixel gating in CI
  • themeAnimationDuration: Duration.zero in the harness — determinism over animation fidelity for static captures
  • Roboto-Medium remains registered under its own family (Roboto Medium); known minor defect — w500 labels render Regular weight in captures

Review

  • GSD code review report committed (6791278); WR-01..WR-05 findings fixed in 4b7a277, f35be8f, 4fd539f, d55dc2e, 118d78d, 6e207bd

Phase 9 executed fully on 2026-08-20 (all 6 plans, atoms + demos + barrel
exports shipped, 09-06 self-check passed) but only 09-03 carried
'status: complete' frontmatter, so the ROADMAP progress count derived
4/6 'In Progress'. This is a bookkeeping correction only — no code change.

- Add status: complete + completed: 2026-08-20 to 09-01/02/04/05/06 SUMMARY
- Flip ROADMAP Phase 9 row to '6/6 | Complete | 2026-08-20'

Unblocks Phase 11 (Verification & Coverage Gate), which depends on
Phases 8, 9, 10 and owns the formal verification sweep.
- Remove 42-line comment+test block at scaffold_selection_actions_test.dart:334-375
- Delete trailing blank line so Test 18 follows prior test with single blank separator
- File goes from 929 to 886 lines (plan expected 887 — plan arithmetic off-by-one)
- Suite now runs zero-skip; 18 non-skipped tests already cover onSelectionChanged via debugSimulateSelection
- Add 11-01-SUMMARY.md with verbatim gate evidence for 11-VERIFICATION.md
- Records the 887-vs-886 plan arithmetic discrepancy (semantic intent satisfied)
- New example/test/capture_images_test.dart pumps all 26 demo widgets
  and writes one PNG per demo to ../images/ (D-02 WRITER harness)
- Uses scaffoldThemeExtensions via canonical _pump wrapper to match
  widget-test rendering
- RenderRepaintBoundary.toImage(pixelRatio: 2.0) produces 1600x1200 PNGs
- Infinite-animation demos (LoadingDemo flickr, AnimationsDemo,
  KitchenSinkDemo pulse, ScaffoldStreamingRichTextDemo cursor) use
  pump(kAnimationSettleTime) instead of pumpAndSettle per
  11-RESEARCH.md Pitfall 1
- All numeric literals named as kCapture* constants at top of file
- No matchesGoldenFile — D-02 hard rule
- Rework _captureWidget to wrap toImage/toByteData/File.writeAsBytes in
  tester.runAsync so the real-async rasterization resolves even when demo
  widgets contain infinitely-repeating animations (ActionButton rotate,
  Loading flickr, ScaffoldAnimatedDisplayPulse, streaming cursor blink).
  Without runAsync the test binding's fake-async zone waited forever and
  the run timed out at the 10-minute limit (Rule 3 - blocking issue).
- Drop the useAnimationSettle flag — every capture now uses a fixed
  pump(kAnimationSettleTime) so infinite animations simply show whatever
  frame is current at the 1s mark.
- Drain pending rendering exceptions via tester.takeException() after
  each capture: ScaffoldChart's X-axis legend Row overflows by 16px at
  the 800px capture width (a pre-existing layout quirk in
  lib/components/scaffold_chart.dart:302, not a harness bug). Pixels are
  still written; the harness is a WRITER, so the warning is discarded.
- 26 PNGs at 1600x1200 (800x600 x pixelRatio 2.0) under package-root
  images/, one per demo in main.dart registry order.
- flutter test test/capture_images_test.dart reports 'All tests passed!'
- dart analyze --fatal-infos (whole example package) clean
- images/ is NOT gitignored (git check-ignore exits 1)
- 26 PNGs at 1600x1200 in images/, one per demo, named per 11-RESEARCH.md
- Harness is WRITER per D-02 (no matchesGoldenFile)
- Two Rule 3 deviations documented: runAsync fix for infinite animations,
  takeException drain for pre-existing chart legend overflow
- Replace '214 tests' with '454 tests' (lines 86, 300)
- Replace 'one demo screen per widget family' with '26 demo screens covering every widget family and the v1.2 atoms'
- Add images/ row to repository layout tree
- Embed one images/<name>.png per demo in the same order as the
  _DemoTile registry in example/lib/main.dart
- Filenames match Plan 11-02 captures exactly; package-root-relative
  paths render on GitHub
- Preamble states 7 ready / 8 thin / 4 primitive-enabled split
- Table copied verbatim from 11-RESEARCH.md §WIDG-45 19-Component
  Coverage Proof (bold stripped from tier cells to keep grep checks
  unambiguous)
- Inserted between Component gallery and Develop
Root cause of broken images: the capture harness used
ThemeData(extensions: scaffoldThemeExtensions) — a default LIGHT
ThemeData with the DARK ScaffoldPalette.defaultPalette bolted on.
Dark-palette text (white, grey) on a light Material scaffold
background was unreadable.

Additionally, flutter_test forces the Ahem test font (solid
rectangle glyphs) for all text rendering, making the images useless
for human viewing.

Fixes:
- Build proper brightness-matched ThemeData (mirrors main.dart:72-85):
  dark uses ScaffoldPalette.defaultPalette, light uses
  ScaffoldPalette.lightPalette, both with ColorScheme.fromSeed
- Load Roboto fonts (Regular/Medium/Bold) from Flutter SDK's
  material_fonts directory via FontLoader, registered under both
  'Roboto' and 'monospace' family names so all widget text renders
  real glyphs
- Capture both dark and light themes per demo (52 PNGs total)
- Update README gallery to reference _dark.png and _light.png
  stacked (dark first, matching demo app default)
…fixes

Widget fixes (root cause — atoms consume bare consumer Text but never
set DefaultTextStyle, so text resolved M3 ColorScheme.onSurface grey
instead of the scaffold palette):
- ScaffoldSelectionActions: wrap SelectionArea child in DefaultTextStyle
  with palette.textPrimary
- ScaffoldTraceList: wrap item.body in DefaultTextStyle before passing
  to ScaffoldDisclosure
- ScaffoldDisclosure: wrap body in DefaultTextStyle with
  palette.textSecondary inside AnimatedSize reveal

Harness fixes (example/test/capture_images_test.dart):
- Load MaterialIcons font (icons rendered as Ahem squares)
- Add PrepareCapture hook to drive interactive state before rasterization
- _expandAllDisclosures: tap only COLLAPSED disclosures (check
  AnimatedSize.child is Padding vs SizedBox.shrink) — tapping an
  already-expanded disclosure collapses it
- _captureWalletSheet: build sheet content directly (connected +
  disconnected states) — demo is button-triggered so static captures
  only showed the button page
- _tapChartCenter / _dragChartRange for chart interaction captures

Regenerate all 52 PNGs with readable text in both themes.
Both _captureWidget and _captureWalletSheet dropped the inner Future<File>
returned by writeAsBytes. Awaiting create().then(...) resolves as soon as
create() finishes, leaving the write un-awaited and silently swallowing
errors. Await both futures sequentially and flush the bytes.
Replaces the magic i<6 iteration bound with an unbounded walk that stops
at the filesystem root. The constant 6 was tied to a specific Flutter
SDK layout and would silently regress to Ahem placeholder squares if
the layout ever shifted. Now throws StateError on failure so the writer
harness fails loudly instead of producing Ahem-renderer PNGs.
Previously the AnimatedSize child swapped between Padding (expanded) and
SizedBox.shrink (collapsed). Because those are different widget types
with no shared key, the element tree replaces the child and swaps the
render object mid-animation, causing a snap rather than a smooth
collapse. Always render the Padding wrapper (with the top padding
gated on the expanded state) and only swap the inner child so the
RenderPadding render object survives the transition.
…sh (WR-01)

Previously the probe call to widget.toolbarBuilder was discarded, and
the overlay builder invoked toolbarBuilder a second time. Side-effectful
consumer builders (analytics, controller allocation, stateful widgets
relying on initState/dispose symmetry) ran twice per toolbar appearance.

Cache the probe widget as _toolbarChild and reuse it in the overlay
builder, so toolbarBuilder is invoked exactly once per refresh. Also
update the toolbarBuilder dartdoc to document that the builder must be
pure and that the same widget is reused for the overlay.
Previously _escapeFocusNode.requestFocus() ran unconditionally on every
toolbar insert, yanking focus out of consumer-controlled fields when
the toolbar was triggered by a keyboard selection or debugSimulateSelection
while focus was elsewhere. Gate the request: only grab focus when no
pointer is mid-selection AND we don't already own focus. Pointer-driven
selections keep SelectionArea's existing focus.
…matedSize

WR-03 changed ScaffoldDisclosure's AnimatedSize child to always be
Padding (with gated top-padding) for smooth collapse animation. The
harness's expansion check (child is Padding = expanded) now always
returns true, so collapsed disclosures were never tapped.

Fix: check the Padding's INNER child — SizedBox.shrink = collapsed,
anything else = expanded. Regenerate disclosure/trace_list images.
…mponents

Bare Text widgets in action_button/animations/bottom_drawer/responsive_grid/
toast/tracer demos, DesktopBodyContainer, Loading, and StringButton now use
ScaffoldPalette.textPrimary/textSecondary instead of theme-default onSurface
grey. The example app's _buildTheme aligns ColorScheme onX slots with the
palette so M3 composites resolve the same colors.
…res painted light primary

Interleaved tester.runAsync (toImage/encode/write) freezes in-flight
AnimatedTheme tickers at their start value, so each light->dark swap on the
reused element tree left the widget-level theme stuck on the light scheme
while already-painted dark layers persisted — every _dark capture with a
plain ElevatedButton rendered the light-scheme primary (#1C6B50) over dark
surfaces instead of the app's dark primary (#8BD5B5). themeAnimationDuration:
Duration.zero swaps themes instantly on the pump frame, immune to the frozen
ticker. Regenerated all 52 demo images; pixel-verified dark primaries.
@Super-Genius
Super-Genius marked this pull request as ready for review August 24, 2026 19:04
@Super-Genius

Copy link
Copy Markdown
Contributor Author

@codex review

@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: 9772dd745b

ℹ️ 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/components/string_button.dart Outdated
Comment thread example/test/capture_images_test.dart Outdated
- StringButton: only default label to palette.textPrimary when no fill
  color is supplied; with a consumer fill, inherit MaterialButton's
  contrast-derived foreground (prevents white-on-white for light fills).
  Regenerated string_button_light.png capture.
- capture_images_test: narrowed the post-capture exception drain — only
  known RenderFlex overflow warnings (ScaffoldChart X-axis legend at
  800px) are tolerated; any other rendering exception now fails the test
  instead of being silently discarded.

Gates: dart analyze --fatal-infos clean, 454/454 tests pass.
@Super-Genius
Super-Genius merged commit 7dafcb9 into develop Aug 24, 2026
2 checks passed
Super-Genius added a commit that referenced this pull request Aug 24, 2026
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