Phase 22 — The Same Villa
05 JUL AT 08:37 PM

Phase 22 — The Same Villa

0 LOVES 2 VIEWS
Flutter guide parity, built again.

UTS Phase 22 — Flutter guide parity (scoping for the Flutter team)

📋 SCOPING DOC — the master plan for the Flutter parity pass. This is the scope, grounding, and decision record for Phase 22; the per-sub-phase implement-ready plans (22.1…22.6) are authored one at a time from here. Grounded 2026-06-27 by two parallel code traces over flutter/gokonversed/lib + a firsthand read of the load-bearing widgets, and re-grounded 2026-06-28 against the actual Flutter source (token file, theme, site config, pubspec, blast-radius greps) — see Code-grounding pass.

Headline reframe: Flutter is NOT greenfield. A prior "Guides Phase 5" effort already shipped a full guide implementation — lightbox (cook + learn + overview + lesson + completion + review + picker), the 4 post-body blocks, progress + block-interaction blocs/repositories, and models. Phase 22 is therefore an UPDATE to bring that existing implementation up to the web's post-UTS state, not a build-from-scratch.

Program: Under the Tuscan Sun (overview). Sequenced last (after the full web version + docs — now true: web Phases 1–21 complete). Owner: Flutter team (with Travis).

Goal (operator, 2026-06-28): web↔Flutter PARITY. The gap was expected and has two sources — (1) pre-existing drift from the original Flutter "Guides Phase 5" build, and (2) newly introduced drift from the UTS web redesign (the g-* restyle + the Phase-20 nonsequential learn-flow). Phase 22 closes both so the Flutter guide experience matches the web. Where they differ, the web is the reference (per the Flutter repo's "kontent web view is the authoritative reference" rule).

Method — two sources, zero guessing

We have everything; nothing here is invented. The upgraded web kontent is the working MVP/prototype, and the existing Flutter app is what we upgrade — so every value, structure, and behavior is read from one of two complete sources, never guessed: - What it should be → the working web kontent source (kon_guide_v2.css for values, the _guide_lightbox/_block_* templates for structure, guide-lightbox.js + compilers for behavior). design-system-spec.md is its portable summary; when summary and source disagree, the source wins. - What's there to change + the app's conventions → the existing Flutter source (flutter/gokonversed/lib/...).

If a sub-phase plan ever states a value/behavior/structure without a file:line from one of these two sources, that's a defect — go read the source. No "recommend", no "derive", no "share or duplicate" as if it were an open choice — find the existing answer and port it.


Code-grounding pass (2026-06-28)

Before writing any sub-phase plan, the load-bearing Flutter files were read firsthand (not the doc prose). This corrects several premises the spec §7 / the earlier trace stated as TODO or assumption. Every number below is measured against the source, not estimated.

Premise (spec/earlier) Code-grounded reality Where
"Bundle BasicGothicWebPro (200/400/700)" — stated as TODO Already bundled as BasicGothicPro (the OTF family), weights 200/400/500/700, wired as SiteConfig.fontFamily. 22.1 reuses it — no font work. pubspec.yaml:190-210, site_config.dart:49
"Tokens → a ThemeExtension (the spec's option)" No ThemeExtension — the app uses none, don't add one. Shipping convention: fixed values → DesignTokens static const; brand → Theme.of(context).colorScheme.primary read at the widget; the app's general tint idiom is brandColor.withValues(alpha:…). But the guide's two color-mix tokens (--g-brand-ink + the brand tints) port to Color.lerp — operator-chosen 2026-06-28, "the kontent web way" (exact, opaque color-mix port over the looser withValues, which only matches over white; and --g-brand-ink has no alpha analog anyway — alpha can't darken toward ink). Adds 0 → 2 Color.lerp uses (confined to gBrandInk/gBrandTint, §7-sanctioned); still 0 ThemeExtension. design_tokens.dart:521-1125, grep: 0 ThemeExtension/0 Color.lerp today
"≈13 surfaces visually stale" 690 references to DesignTokens.guide*/meGuides* across 25 files consume the rainbow (grep-verified 2026-06-28). (Blast-radius table below.) grep
Track B: "Flutter gates nav (sequential)" True but learn-mode-only. Cook Mode is already non-gated (free scroll — matches web). Only Learn Mode gates (canAdvance :82-90). And the existing learn mode auto-advances to completion on isGuideComplete, has no partial-completion page concept, and has no reset — so D22.1 is a real rework, not just "un-gate." guide_progress_state.dart:82-90, guides_phase5c_guide_progress.md, guides_phase5e_learn_mode.md
(not previously flagged) Two white-label leaks in the tokens. guideLightboxCloseHoverColor = #CF0015 (RI brand red) is grep-verified DEAD (0 readers) → delete in 22.1 (clean). guideMaterialQuantityColor = #1565c0 (blue) has 1 live reader (material_row.dart:79, the qty Text color) → can't delete in 22.1 without going red; repoint → gInk at 22.3 with the material restyle (a visible blue→ink fix). design_tokens.dart:824,606; material_row.dart:79
"--g-on-brand needs a new source (derive or add a SiteConfig field), or = per-post header_text_color" WRONG on both — verified against source 2026-06-28. The web injects only --kon-brand-color per site (base.gohtml:25-28); --g-on-brand is never overridden → resolves to its :root default #fff for every site in prod. The guide .g-band = --g-brand (the SITE brand) fill + --g-on-brand (white) text — not the post-detail header's per-post header_background_color/header_text_color (a different surface). Flutter: bandBg = colorScheme.primary, onBrand = Colors.white. The header_text_color mapping is the web's documented-but-unwired light-brand override — mirror only if/when the web wires it. See D22.5. base.gohtml:25-28, kon_guide_v2.css:17,24, post_header_zone.dart:57-60
(not previously flagged) §5C block-chrome may be silently dropped. Web renders heading/show_divider/hidden/sort_order in the dispatcher; Flutter has no shared dispatcher → must replicate it in content_block_renderer.dart or authored headings vanish. Verify in code at 22.3. spec §5C; content_block_renderer.dart
(confirmed) The data layer is backend-unchanged and complete — models already carry every field (incl. future-reserved quantityNumeric/slug); snake_case; StepCollectionItem.ID hydration key; D9 redaction; current_step_index vestigial. Phase 22 is pure client-side UI + the learn-flow behavior flip. guides_phase5a/5b/5c* + models

⚠️ A correction to the automated trace (retained): an initial behavior trace reported "7/7 parity, no corrections needed." That is over-optimistic — verified firsthand, it is wrong on the headline point (see Track B). The assessment below is the corrected, code-grounded one. Lesson: verify load-bearing agent claims in code, especially rosy "all parity" verdicts.


The three contracts Flutter must reach parity with

Track Source of truth One-line state of Flutter today
A. Visual (the g-* redesign) design-system-spec.md §5/§7 (the §7 Flutter mapping is already written) + the catalog docs/mockups/under_the_tuscan_sun/catalog.html Built to the PRE-UTS design → 690 token refs across 25 files encode a per-type/material-type rainbow + block÷lightbox value duplication. Needs the token rewrite (22.1) + a comprehensive restyle. The biggest single chunk of Phase 22.
B. Behavior (honest learn-flow) phase-20-learn-completion-review.md "As-built + Flutter parity" (the 7-point contract) Honest, but SEQUENTIAL in learn mode — Flutter gates canAdvance, auto-advances to a celebration-only completion page, no partial concept, no reset. DECIDED (D22.1, 2026-06-28): adopt the web's nonsequential model — full parity.
C. Architecture (independent tracking) phase-21-tracking-reconcile.md D21.1 Already parity — separate blocs/repositories/endpoints; just preserve it.

Current-state inventory (what Flutter already has)

  • Lightbox lib/shared/widgets/organisms/guide_lightbox/: guide_lightbox.dart (shell), guide_lightbox_cook_mode.dart, guide_lightbox_learn_mode.dart, guide_lightbox_overview.dart, guide_lightbox_lesson_page.dart, guide_lightbox_completion.dart, guide_lightbox_review.dart, guide_lightbox_materials.dart, guide_lightbox_step.dart, guide_lightbox_footer.dart, guide_picker.dart.
  • Post-body blocks lib/content_blocks/widgets/: step_block.dart, step_collection_block.dart, material_item_block.dart, material_collection_block.dart. Dispatched by lib/content_blocks/content_block_renderer.dart (the §5C chrome surface).
  • Atoms/molecules lib/features/guides/ui/: atoms/material_type_badge.dart, atoms/step_badge.dart, atoms/timeline_circle.dart, atoms/tip_box.dart, atoms/explanation_box.dart, molecules/answer_option_row.dart, molecules/flashcard_panel.dart, molecules/material_row.dart, molecules/guide_thumbnail_strip.dart. (The rainbow lives here: material_type_badge/step_badge/timeline_circle ARE the per-type color; the restyle neutralizes or replaces them.)
  • State/data lib/features/guides/: bloc/guide_progress_bloc.dart (+ event/state), bloc/block_interaction_bloc.dart (+ event/state), data/guide_progress_repository.dart, data/block_interaction_repository.dart, models (guide, step, step_collection, step_collection_item, material_collection, material_collection_item, step_answer_option, guide_material, guide_metadata, guide_progress_data, user_block_interaction, user_guide_info). Backend-unchanged; no model/repo work in Phase 22.
  • Tokens lib/config/design_tokens.dart:521-1125 — ~180 static const guideLightbox*/guide*/meGuides* tokens from the Phase-5c spec. No ThemeExtension.
  • Existing Flutter plan docs (read these): flutter/gokonversed/docs/plans/guides_phase5{a,b,c,e,f}*.md (esp. 5c_css_tokens, 5c_guide_progress, 5b_block_interactions, 5e_learn_mode). These document the pre-UTS build (rainbow tokens + sequential learn mode) — they are what exists, not the target.

Token blast-radius (24 consuming files, DesignTokens.guide*/meGuides* ref counts — 690 total)

 78  content_blocks/widgets/step_collection_block.dart
 63  guide_lightbox/guide_lightbox_overview.dart
 53  guide_lightbox/guide_lightbox_cook_mode.dart
 48  content_blocks/widgets/step_block.dart
 43  guide_lightbox/guide_lightbox_step.dart
 41  guide_lightbox/guide_lightbox_review.dart
 37  guide_lightbox/guide_lightbox_lesson_page.dart
 36  features/guides/ui/pages/me_guides_page.dart
 33  guide_lightbox/guide_lightbox_footer.dart
 32  guide_lightbox/guide_picker.dart
 28  features/guides/ui/atoms/material_type_badge.dart
 25  features/guides/ui/atoms/step_badge.dart
 24  features/guides/ui/molecules/answer_option_row.dart
 23  features/guides/ui/molecules/flashcard_panel.dart
 23  content_blocks/widgets/material_collection_block.dart
 22  guide_lightbox/guide_lightbox_materials.dart
 18  features/guides/ui/molecules/material_row.dart
 13  features/guides/ui/atoms/timeline_circle.dart
 12  guide_lightbox/guide_lightbox_completion.dart
 11  features/guides/ui/atoms/tip_box.dart
 11  features/guides/ui/atoms/explanation_box.dart
  6  features/guides/ui/molecules/guide_thumbnail_strip.dart
  4  guide_lightbox/guide_lightbox.dart
  3  features/posts/ui/pages/post_detail_page.dart
  3  content_blocks/widgets/material_item_block.dart

Track A — Visual parity (the main work)

Flutter's guide UI was built to the pre-UTS design (Phase-5c tokens = a per-type color "rainbow", larger thumbs, full-width media, icon-laden callouts, bordered cards, no gallery disclosure, and a deliberate block÷lightbox value split — step disc 24 block vs 32 lightbox; checkbox blue block vs green lightbox; etc.). UTS replaced all of that and unifies the block/lightbox values into one g-* system. The authoritative target is design-system-spec.md §5A–F + §7 (Flutter mapping), which already specs every component as Flutter widgets (ThemeExtension tokens, Color.lerp for the unavailable color-mix, weights 200/400/700, the gallery Show N strip, ExpansionTile material sections, 48dp media-type thumbs, the step/question/flashcard widgets, step_collection chrome, the full-screen-modal lightbox shell). The catalog is the visual acceptance reference.

The token foundation (22.1) — Flutter's "web Phase 1"

Most g- values are fixed (neutral greys + semantic green/red/amber + sizes/radii) → plain DesignTokens const, ported from spec §5A–F. Only a few are brand-derived*, and the app already has a shipping convention for each (no new mechanism — D22.4): --g-brand = colorScheme.primary; brand-tints (e.g. `color-mix(brand 6%,

fff)) =gBrandTint(brand,.06)Color.lerp(white,brand,.06)(exact port ofcolor-mix);--g-on-brand` =

Colors.white (never overridden on the web — verified, D22.5); --g-brand-ink = Color.lerp(brand,ink,.45). Fix the 2 white-label leaks.

Additive migration (mirrors the web exactly). Rewriting the ~180 tokens in place would break all 690 references at onceflutter analyze red for the whole phase. The web solved the identical problem by building kon_guide_v2.css alongside kon_guide.css, migrating per-phase, and retiring legacy last. The Flutter analog: - 22.1 adds the GuideTokens ThemeExtension + g- token surface; leaves the old DesignTokens.guide* consts in place → analyze stays green, zero widgets change yet. - 22.3 / 22.4 / 22.5 migrate each surface's widgets onto the new tokens. - Final cleanup (end of 22.6)* deletes the now-dead old consts — the "kon_guide.css retirement" analog.

Per-surface verdict (from the delta trace + 2026-06-28 grounding)

Surface Flutter file Verdict
Lightbox shell / masthead guide_lightbox.dart STALE — transparent barrier (→ opaque black); no brand-band masthead, no author medallion, no eyebrow, no ultralight title. Cover fixed-height 260/180/160 (guideLightboxCover*) → natural aspect (§5F).
Metadata grid guide_lightbox_overview.dart:352 STALE — plain metaValue; no value-dispatch (URL→link, ISO-8601→"N min"), no key humanization
Cook materials guide_lightbox_materials.dart STALE#e3f2fd/#1565c0 group headers; not independent expanders w/ tint bars; no Show N gallery
Cook steps guide_lightbox_step.dart STALE — disc 32→30dp + blue→brand, no 3-bar/? glyph, full-width media→48dp thumb, tip/duration icons; checkbox green→brand
Cook footer guide_lightbox_footer.dart CLOSE — progress fill blue(learn)/green(cook) → unify both to --g-ok
Learn overview guide_lightbox_overview.dart STALE — no brand band; per-type badges; icon-laden stats/sections; static CTA label (see Track B)
Learn lesson page guide_lightbox_lesson_page.dart CLOSE — structure ok; disc/gallery/tip/checkbox restyle
Learn completion guide_lightbox_completion.dart CLOSE — score logic ok; styling + the partial branch (Track B)
Learn review guide_lightbox_review.dart CLOSE — timeline ok; disc halos → brand/--g-ok/--g-bad only (drop the 3px halos)
Guide picker guide_picker.dart STALE — recipe/course/food/cocktail badge rainbow → neutral + brand
step block content_blocks/widgets/step_block.dart STALE — disc, duration icon, gallery, tip icon, checkbox color
material item block material_item_block.dartmaterial_row.dart STALE — 60→48dp thumb, gallery, per-type badge
material collection block material_collection_block.dart STALE — bordered card + alt-row green accent → independent tint-bar expanders; ungrouped shell
step collection block step_collection_block.dart CLOSE — timeline ok; disc colors + collapsed galleries; drop the 2px connector + 3px halos
block chrome (heading/divider/hidden) content_block_renderer.dart VERIFY (§5C) — no shared dispatcher; confirm headings/dividers aren't dropped

Prioritized component gaps (build/replace, reused everywhere)

  1. Token foundation (22.1) — rewrite the g- DesignTokens consts (kill the per-type rainbow; add --g-ok/--g-bad/--g-faint/--g-tint/--g-line; correct sizes — 48dp material thumb, 64dp gallery tile, 30dp step disc, 6px radii), reuse BasicGothicPro (200/400/700), and wire the few brand-derived tokens (--g-brand = colorScheme.primary; the 2 color-mix tokens --g-brand-ink + brand-tints via Color.lerp — operator-chosen web-faithful port; --g-on-brand = Colors.white, never overridden on the web — D22.5). Delete the 1 dead leak const (the live material-qty leak repoints at 22.3). Load-bearing — every surface consumes it (the Flutter analog of web Phase 1).*
  2. Gallery Show N disclosure — collapsed count badge + neutral "Show N" toggle → expanded horizontal ListView strip (64dp tiles, snap), tiles by media_type, tap → the app's existing media/gallery viewer. Affects all 4 blocks + the lightbox materials. Currently missing entirely — the existing guide_thumbnail_strip.dart is an always-open strip, not the disclosure.
  3. Per-type rainbow → brand + neutral across ~5 surfaces (discs, headers, badges, type labels).
  4. Brand-band masthead (lightbox) — cover (natural aspect, no crop) + author medallion (−42dp) + eyebrow + ultralight title + stat line. Band colors = --g-brand fill + --g-on-brand text = colorScheme.primary + Colors.white (the SITE brand + white, matching the web .g-band; not the post-detail header's per-post colors) — D22.5.
  5. Step/question disc glyphs — 3-bar (instruction) / ? (question); ordinal in step_collection.
  6. Metadata value-dispatcher — URL→link, ISO-8601→"N min", humanize key, skip visible=0.
  7. Independent material-collection expandersExpansionTile-style, tint-bar headers, all open, not mutually exclusive (replace the bordered card + alt-row green accent).
  8. Icon removal — tips/explanations/duration rely on left-edge color + text, not icons (note: Flutter CLAUDE.md mandates bootstrap_icons, no SVG — the 3-bar/? glyphs are drawn shapes, not SVG assets).

Track B — Behavior parity (one real decision, learn-mode only)

The web's Phase-20 contract is operator-confirmed NONSEQUENTIAL: free nav (Next never gated) + an honest partial end-page ("You've completed N of M", no trophy, a Continue affordance). Flutter's Cook Mode is already non-gated (free scroll, matches web); only its Learn Mode diverges — it gates canAdvance, auto-advances to a celebration-only completion page on isGuideComplete, has no partial-completion page concept, and (per 5e) has no reset. So Track B is a learn-mode rework, not just "un-gate."

Evidence (verified firsthand): guide_progress_state.dart:82-90canAdvance returns isItemCompleted(item.id) for learn-mode lesson pages, and guide_lightbox_footer.dart wires Next as enabled: !isLastPage && state.canAdvance. So Next is disabled until the current step is completed, and the completion page (currentPage > totalSteps) is only reachable when every step is done — Flutter is "honest" trivially (it can never display a partial completion page), but by the opposite mechanism to the web.

Corrected per-point assessment (Phase-20 7-point contract)

# Contract point Flutter today Verdict
1 Honest completion (value-based; partial → "N of M", no trophy) isGuideComplete = completedCount >= totalSteps is value-based ✓, BUT there is no partial-completion branch — the page is gate-unreachable while partial Conditional — honest only under gating; the partial branch is missing
2 Free / nonsequential nav (Next never gated) Gated on current-step completion (canAdvance) ❌ NOT parity — the headline divergence
3 Score on show; omit on partial Score computed on show ✓ (excludes flashcards); omit-on-partial is moot (no partial page) Partial — needs the omit branch once nav is freed
4 Review returns to origin isReviewMode toggle preserves currentPage (guide_progress_bloc.dart review handlers) ✅ parity
5 Adaptive resume CTA (Start / Continue / Review) Resume action present (resumePage → first incomplete), but the label is static ("Start Course" always) — guide_lightbox_overview.dart:395-430 Partial — add Continue/Review labels
6 Done affordance on completion Uses the global close X, not a dedicated Done button Add the Done button (parity, D22.2) — keep the X too, as on web
7 Submit styled to match mark-complete token-driven; rolls into Track A Visual

Contract C (independence): ✅ already parity — GuideProgressBloc + /progress/guide/* vs BlockInteractionBloc + /progress/block/* are fully separate (separate repos, blocs, models, endpoints); the same shared step in a guide vs a post block tracks separately. Just don't regress it.

✅ D22.1 (DECIDED — operator, 2026-06-28): adopt the web's NONSEQUENTIAL model — full parity

Flutter ports the web's Phase-20 learn-flow exactly. Track-B work spec (concentrated in guide_progress_state.dart / guide_progress_bloc.dart, guide_lightbox_footer.dart, guide_lightbox_overview.dart, guide_lightbox_completion.dart):

  1. Un-gate navcanAdvance must stop returning isItemCompleted(item.id) for learn-mode lesson pages; Next is always enabled (except on the completion page). This is the core flip from sequential → free.
  2. Stop the auto-advance — the existing learn mode jumps to the completion page on isGuideComplete. With free nav the learner reaches the completion page by navigating (it's the page after the last lesson); completion stops being an auto-jump and becomes a normal reachable page.
  3. Honest partial completion page — once nav is free, the completion page becomes reachable while partial. Add the partial branch: no trophy, header "You've completed N of M lessons", a Continue affordance → first incomplete lesson (reuse resumePage). Full celebration (trophy + score) only when isGuideComplete.
  4. Omit score on partialguide_lightbox_completion.dart already computes the score on show with an all-non-flashcard-question denominator (correct); omit it on the partial page (unanswered questions would misread as wrong). Full score only when complete.
  5. Adaptive CTA label — the overview button (guide_lightbox_overview.dart:395-430) already resumes via resumePage, but its label is static. Make it adaptive: none done → "Start Course"; partial → "Continue"; all done → "Review Results". Overview stays the landing (no auto-jump).
  6. Done button (point 6, D22.2) — add a dedicated Done button on the completion page (reusing the close action) to match web D20.5; keep the global close X too (as on web).

Already parity (don't rebuild): review-returns-to-origin (point 4 — isReviewMode preserves currentPage) and the client-side resume computation (resumePage, not a server current_step_index).

Note on the pre-UTS "Learn Mode has no reset" rule (5e SA-2): that was a client-only restriction (the overview/footer simply didn't show a reset). Phase 20 didn't add learner-facing reset either (retake = the admin reset). So Track B does not add a reset to learn mode — it stays absent on both platforms; this is parity, not a gap.


Proposed flat sub-phases (the per-doc breakdown — authored one at a time from here)

Mirrors the web's UTS grouping, foundation-first (each consumes the prior); additive token strategy keeps flutter analyze green throughout.

  • 22.1 Token foundation — rewrite the g- DesignTokens consts by porting the exact values from static/css/kon_guide_v2.css (the working web module — spec §5A–F mirrors it); wire the few brand-derived tokens (--g-brand = colorScheme.primary; the 2 color-mix tokens --g-brand-ink + brand-tints via Color.lerp — web-faithful port, operator-chosen; --g-on-brand = Colors.white); reuse BasicGothicPro; delete the 1 dead leak const (live material-qty leak → 22.3); leave old consts in place (additive). Touches config/ only → zero widget churn yet. Gate: the new tokens render the catalog atoms under a dark brand (#1C1D20) and a light brand (#e7c84a)* with the §6 contrast guardrail holding (the web Phase-1 dual-brand check). Full plan: phase-22.1.
  • 22.2 Shared components — the Show N gallery disclosure (replace guide_thumbnail_strip.dart), the 48dp media-type thumb, the independent ExpansionTile section, the disc glyphs (3-bar/?/ordinal), neutral labels (material_type_badge/step_badge → neutral or removed), the de-iconed tip/explanation/duration atoms. Verify the §5C chrome path in content_block_renderer.dart. Full plan: phase-22.2.
  • 22.3 Post-body blocks — restyle step_block / material_item_block / step_collection_block / material_collection_block onto 22.1+22.2.
  • 22.4 Lightbox shell + cook mode — opaque scrim, brand-band masthead, metadata dispatcher, cook content (guide_lightbox.dart/_materials/_step/_cook_mode/_footer).
  • 22.5 Lightbox learn mode + behavior (D22.1) — overview (brand band + adaptive CTA), lesson, completion, review + the nonsequential flip (guide_progress_state/_bloc, _overview, _lesson_page, _completion, _review, _footer).
  • 22.6 Picker + polish + parity smoke — guide picker (guide_picker.dart) + me-guides page; delete the dead old DesignTokens.guide* consts (the legacy-retirement analog); on-device smoke against the catalog + the kontent web reference.

Each sub-phase: code-grounded plan (read the surface's source, cite file:line) → mockup/widgetbook-or-screenshot review → operator sign-off → implement → flutter analyze + tests + on-device smoke (the operator validates Flutter on a physical device, e.g. via Marionette).

Scope map: phase-22-CROSS-REFERENCE.md is the standing "what's different, and which sub-phase owns it" matrix (web target × current Flutter × owning phase). Consult it to tell deferred-work (later sub-phases not done yet) from a real gap — most visible differences in the live app today are simply 22.3–22.5 not built, not bugs or scope creep.


Risks & notes for the Flutter team

  • White-label rule (Flutter CLAUDE.md): never hardcode brand values — all color flows from site_config.dart / theme. The g- "brand" token = the site brand; semantic green/red/amber are fixed. 22.1 fixes the 2 existing leaks* (guideLightboxCloseHoverColor, guideMaterialQuantityColor).
  • Icons: Flutter mandates bootstrap_icons (no flutter_svg). The 3-bar/? step-disc glyphs are drawn shapes (CustomPaint/Container), not SVG assets — fine. Icon removal from tips/duration matches UTS.
  • color-mix unavailableColor.lerp (spec §7 gives the exact factors: brand-ink = lerp(brand,ink,.45); tints = lerp(brand,white,.88–.94)).
  • Cover aspect: the masthead cover must be natural aspect (no BoxFit.cover crop); the current overview uses a fixed height (guideLightboxCover*) — change per §5F.
  • Don't regress independence (Contract C) — keep the two blocs/repos separate.
  • current_step_index is vestigial (Phase 21 D21.2) — resume is client-side (resumePage); do not start reading a server resume index.
  • Cover authoring is web-only — the Studio guide-cover picker (Phase 19) + media-picker pagination (19a) are web-authoring; Flutter just reads guide.cover_image and inherits whatever lands. Zero Flutter work.
  • Visual acceptance references not yet inspectedcatalog.html (rendered g-* states) and Matt's matt-recipe-detail-draft.png are the sign-off images; open them at 22.1+ build time to match pixels, not just prose.
  • Reference, in order (the actual SOURCE, not just the spec): 1. The working web kontent source — the MVP/prototype, authoritative for every value, structure, and behavior: static/css/kon_guide_v2.css (exact px/colors/states), the templates internal/http/web/kontent/v1/templates/partials/organisms/_guide_lightbox.gohtml + _block_{step,step_collection,material,material_collection}.gohtml (structure/order/conditionals) + the _mat_thumb/_mat_gallery molecules, and the JS static/js/kontent/utils/guide-lightbox.js + the block/collection-item compilers (behavior/state machine). 2. design-system-spec.md §5A–F/§7 + the catalog catalog.html + Matt's draft — the portable summary of #1 (use to navigate; when it and the source disagree, the source wins). 3. The existing Flutter sourceflutter/gokonversed/lib/... (what we upgrade) + the guides_phase5* docs (what's there — pre-UTS, not the target).

Decisions

Governing rule (D22.1 Goal): parity — any web/Flutter divergence resolves in favor of matching the web, unless a platform constraint forces an equivalent (e.g. the app's native media viewer; bootstrap_icons instead of SVG, Color.lerp instead of color-mix).

  • D22.1 — ✅ RESOLVED: nonsequential, full parity (see the Track-B work spec). Learn-mode only (cook is already free).
  • D22.2 — Done affordance — ✅ RESOLVED by parity: add the dedicated Done button on the completion page to match web D20.5 (it reuses the close action). The close X also stays, as on web.
  • D22.3not a product decision — sub-phase granularity / sequencing is the Flutter team's work-breakdown prerogative. No operator input needed.
  • D22.4 — Token mechanism (22.1).RESOLVED: follow the app's existing convention — no new mechanism. The app uses no ThemeExtension and no Color.lerp (grep-confirmed). Its shipping pattern: fixed g- values → DesignTokens static const (ported from kon_guide_v2.css); --g-brandcolorScheme.primary. Both brand blends use Color.lerp--g-brand-ink because alpha can't darken toward ink (no app analog), and the brand-tints by operator decision (2026-06-28, "follow the kontent web way"): the exact, opaque port of the web's color-mix, chosen over the looser withValues(alpha:) idiom. gBrandInk(brand)=Color.lerp(brand,ink,.45), gBrandTint(brand,pct)=Color.lerp(white,brand,pct) — §7 names both. --g-on-brandColors.white (D22.5). (An earlier draft "adopted a GuideTokens ThemeExtension" — rejected: that pattern exists nowhere in this production app; introducing it would be inventing, not upgrading.)*
  • D22.5 — --g-on-brand / band-fill source (22.1).RESOLVED — corrected by the dynamic/static verification (2026-06-28): the guide band is the SITE brand + white, NOT per-post header colors. Source- verified: base.gohtml:25-28 injects only --kon-brand-color per site, so --g-on-brand is never overridden and resolves to its :root default #fff for every site in prod. The web .g-band is background:var(--g-brand); color:var(--g-on-brand) → Flutter bandBg = colorScheme.primary (the per-site brand), onBrand = Colors.white. The per-post header_background_color/header_text_color belong to the post-detail header (post_header_zone.dart:57-60), a different surface — the guide band does not use them. The CSS comment (kon_guide_v2.css:18-23) documents a light-brand override mapping --g-on-brandheader_text_color, but it is unwired on the web; Flutter mirrors it only if/when the web wires it. (Both prior framings were wrong: "derive from luminance / add a SiteConfig field" missed that the value is simply white; and "reuse the per-post post_header_zone colors" mapped the wrong surface onto the band. The post_header_zone ?? white idiom remains the reference for how the app expresses on-brand-with-white-fallback.)

→ No open operator decisions. The plan is code-true and workable; sub-phase docs can begin.

Acceptance (program-level)

Flutter guide surfaces render the catalog's states (the visual acceptance reference) under a dark and a light brand; the learn-flow matches the D22.1 decision (nonsequential + honest partial end-page); independence preserved; flutter analyze clean at every sub-phase (the additive token strategy guarantees it); on-device smoke on the main site. Per-sub-phase gates as above; the dead old DesignTokens.guide* consts are deleted in 22.6.


UTS Phase 22 — Full Flutter ↔ web-kontent guide parity audit

Why this exists: after a machine restart + cache clear, the operator asked for a complete review — the worry being missed items, style OR functional, anywhere between the Flutter app and web kontent for guides. This is NOT scoped to the in-flight 22.5 steps; it sweeps the entire guide surface — the shipped 22.2/22.3/22.4 "done" work included — bidirectionally (find what's MISSING and what's DEAD).

Method: 9 parallel read-only agents, one per surface + one for auth/verifications + one functional map + one bidirectional style sweep. Each enumerated the FULL facet list from the web source (kon_guide_v2.css, _guide_lightbox.gohtml, the 4 _block_* templates, guide-lightbox.js, block-interaction-bloc.js, the compilers) and diffed the Flutter value, classifying MATCH / DIVERGE / GAP / DEAD / BUG with file:line on both sides. Web (kontent) is authoritative except the operator-locked Flutter-leads (D22.5.a/b/c, W12).

Empirical gates (this session, run — not claimed): flutter analyze = 23 issues, ALL pre-existing share_plus deprecations, 0 guide-related. flutter test = 313 passed. Working tree clean except the 3 expected local-only files. 7 commits on master, none pushed (GoLand push still owed).


Headline

The shipped work is in far better shape than "we might have missed things" feared — but the sweep did find real misses, and they cluster in two places: (1) the collection-block style surfaces that the 22.3 "converged" audit left on legacy tokens while fixing only the standalone step block, and (2) the guide picker, which is shipped in Flutter still on rainbow badges while web has already been de-rainbowed to neutral g-*.

  • The three highest-risk behavior surfaces are verified correct in code: the honest-completion end-page cannot reproduce the old false-trophy bug (numeric-count gate, no DOM-attribute path); nonsequential nav (canAdvance fully removed); and fresh-reopen MC/TF scoring is data-backed (backend emits selected_option_id + is_correct).
  • Zero shipped functional bugs of consequence. One minor self-healing transient (assessment-block Reset-All) and one auth risk-amplification (offline near-expiry logout) are the only functional findings on shipped code.
  • All 8 web-fix→Flutter behavioral rows re-verified CONFIRMED in code (hydration re-green, anti-spoiler redaction, disc=server-verdict, bi-circle-until-submit, completed-greening+M8 strike, reset flows, submit, flashcard reveal). No MISSING functional behaviors; no DEAD Flutter behaviors.

Measured confidence

Surface Facets Clean MATCH Per-surface
Completion (shipped) 46 41 94%
Lesson chrome (shipped) 30 27 92%
Footer + behavior (shipped) 42 39 93%
Overview (shipped) 44 33 90%
Functional map (blocks+cook) ~42 ~40 92%
Style sweep (22.2/22.3/22.4 done) ~104 ~88 93%
Shipped subtotal ~308 ~268 ~87% clean, ~96% after the identified fixes
Review page (UN-BUILT step 4) ~42 gap spec 90% enumeration
me-guides + picker (22.6) ~30 88%
Auth V7 (A) / verifications (B) / token sweep (C) 90 / 95 / 98%

Overall shipped-parity ≈ 87% clean-match measured; ≈ 96% after the enumerated fixes land. Residual (the honest ~4–13%) is almost entirely RENDER-QUESTIONS — ~20 facets where the rendered web differs from its own source CSS (Step 3 already proved this class of trap) and only a live computed-style A/B can settle them. The dev env is down, so those are owed to a live smoke pass.


LIVE A/B RESULTS (2026-07-03, dev up, web computed styles captured on the Mixology guide)

Ran the measured pass on main/Mixology (web anon, uts-smoke playwright) — the goal is Flutter matches web, so these are the definitive TARGET values:

  • D1–D5 collection block — CONFIRMED with exact web computed styles (post-body .o-block-step-collection):
  • .g-sectiontitle = 18.4px / w500 / #222 (gInk) / mt 25.6 / mb 8 (Flutter: 16/w600/#333 → fix).
  • .g-step-text = 15px / w400 / #3a3a3a (gBody) / lh 24 (Flutter: 14.4/#555 → fix).
  • .g-md-v desc = 13.5px / w400 / #3a3a3a / 0 pad (Flutter: #757575 +pad → fix).
  • .g-check label = "Mark as done" / 13px / w400 / #6b7280 / mt 10.4 (Flutter: "Done"-when-checked / 13.6 / #757575 → fix text + metrics).
  • C-1 anon Start button — CONFIRMED exact web target: text "Log in to start", btn-outline-secondary, no icon, bg transparent, color+border #6c757d, 16px / w400, radius 4, opacity 0.65. Flutter does gray-fill #DEE2E6 + keeps arrow → fix to outline/transparent/no-icon. (The lesson Mark-Complete anon state is largely unreachable — web hides overview nav + Start is disabled for anon — so it's low-value; deprioritized.)
  • C-2 progress fill — RESOLVED as NON-ISSUE. kon_guide_v2.css:387-390 sets both cook + learn footer fill to --g-ok (green); :185/:212 same for post-body; JS :796-815 only toggles --complete (also green). No blue-until-100% in any UTS surface (agent 8 misread). Flutter's always-green is CORRECT parity — no fix. Only the pre-g me-guides page uses blue (separate ambiguous surface, D-3).
  • Overview masthead / de-blued callout / .g-md metadata / required-materials all render correct on web (visual confirm).

Net effect on the plan: the real, actionable parity gaps narrow to D1–D5 (collection block) + C-1 (anon Start button) — both now with exact targets — plus the un-built review page (step 4) and the rainbow picker. C-2 drops out.

FIXES LANDED + SMOKED (2026-07-03) — working tree, uncommitted

Applied to design_tokens.dart (D1/D2/D3/D5 + O-D1 token values) + guide_lightbox_overview.dart (C-1 Start button branch + O-D4). All measure-confirmed from the live Flutter widget tree (marionette) against the web computed styles: - D1 collection .g-sectiontitle18.4 / w500 / #222 (widget tree exact) ✅ - D2 collection .g-step-text15 / 1.6 / #3a3a3a ✅ - D3 collection desc .g-md-v13.5 / #3a3a3a ✅ (left the bottom:12 pad — spacing nuance, deferred) - D5 checkbox → size 16 / gap 7.2 / mt 10.4 / label 13 / #6b7280 (shared with standalone step block); "Mark as done" label kept green-on-complete - O-D1 scroll-hint → #9AA0A6 (gFaint) ✅; O-D4 "What you'll learn" → w700 ✅ - C-1 anon Start → transparent gray #6c757d outline, no arrow, opacity .65, "Log in to start" (operator-smoked anon ✅); authed Start → brand fill, 16px/w600, arrow 16 (widget tree ✅) - D4 DROPPED (refuted) — web JS rewrites the label to "Done" on complete (block-interaction-compiler.js:35,389 + guide-lightbox.js:260,448); Flutter's isChecked ? 'Done' : 'Mark as done' was already correct. - C-2 confirmed NON-issue — Flutter green partial fill at "Lesson 3 of 9" matches web (green everywhere). - Font verified — whole app on BasicGothicPro (= web BasicGothicWebPro); fontFamily set once in theme.dart; no per-widget overrides, no GoogleFonts; my weights (500/700/400) are bundled faces. - Gates: flutter analyze clean; flutter test 313 passed (no regressions). Method lesson reinforced: verify each candidate fix against the render before applying (D4 would have broken parity).


A. Functional bugs / issues on SHIPPED code

ID Severity Issue Evidence Fix
BUG-RISK-A1 ⚠️ Medium Offline near-expiry → silent logout. The new proactive SessionRefreshScheduler calls refresh() when a token is near expiry; doRefresh() maps ANY Err (network blip, 5xx, real 401) → nullonSessionExpired() → logout. Offline at the 15-min boundary evicts an otherwise-valid session. Pre-existing in the reactive interceptor, but the scheduler now fires it proactively without user action. main.dart:104-118, session_refresh_scheduler.dart:69-77 Distinguish transient/network failures (don't log out); only onSessionExpired on a definitive auth rejection.
BUG-F1 Minor (self-healing) Assessment-block Reset-All transient. "Reset all progress on this page" optimistically clears ALL block interactions, so an answered assessment block (resettable=false, server preserves it) visually resets until the next PostDetail load. Web does location.reload() → stays. block_interaction_bloc.dart:388-392, post_detail_page.dart:730 Re-hydrate (fire PostDetailLoad) after reset-all, or skip non-resettable blocks client-side. Needs live smoke (page mixing resettable + assessment blocks) to confirm severity.
O-BUG1 Minor Empty-but-present material collection → orphan 24px gap on the overview (unconditional SizedBox(24) before a self-shrinking GuideLightboxMaterials; web guards {{ if $mc.Edges.Items }}). guide_lightbox_overview.dart:96 Guard the SizedBox on items-non-empty.
P-BUG1 Minor me-guides in-progress fill = green for non-course, diverging from web's always-blue; ternary is also redundant. me_guides_page.dart:204-208 Part of the progress-fill-color reconcile (§C-2).
MINOR-F2 Cosmetic (unreachable) step_block reset clears _selectedOptionId but not _flashcardRevealedLocal (collection clears both). Only reachable for anon flashcards, which have no reset affordance → never surfaces. step_block.dart:541-549 Align for consistency.

B. Real STYLE MISSES on "done" surfaces — the core of the operator's worry

The 22.3 pass fixed the standalone step block but left the collection block + material collection on legacy Phase-5a tokens. These are genuine, previously-unreported divergences.

ID Severity Facet Web Flutter Fix
D1 Medium Collection .g-sectiontitle 18.4px / w500 / gInk #222 16 / w600 / #333 step_collection_block.dart:107,173, material_collection_block.dart:45; tokens :795-797 → 18.4/w500/gInk
D2 Medium Collection .g-step-text body 15 / gBody #3a3a3a / 1.6 14.4 / #555 step_collection_block.dart:241,463; tokens :803-805 → gStepTextFontSize 15 + gBody
D3 Medium Collection desc .g-md-v 13.5 / gBody #3a3a3a, 0 bottom pad #757575, +13.6px, +12px bottom pad step_collection_block.dart:124,189, material_collection_block.dart:62 → gBody / 13.5
D4 Medium (content) Checkbox label when checked "Mark as done" (always; only greened) "Done" step_block.dart:488, step_collection_block.dart:666, guide_lightbox_step.dart:253 → keep "Mark as done" + green. ⚠️ RENDER-Q: confirm the JS doesn't itself rewrite to "Done".
D5 Low .g-check metrics (mt 8 vs 10.4, gap 8 vs 7.2, 13.6 vs 13, #757575 vs #6b7280, size 17.6 vs 16) web css:105-106 step_block.dart:459-499, step_collection_block.dart:640-676 align to g- values
D6 / L2 Low Cook-lightbox Submit padding 7.2/16 vs web 8/19.2 (also affects the learn lesson Submit) css:138 guide_lightbox_step.dart:316-319 use the [data-guide-lightbox] override values
D7 Low (render-Q) Collection progress-label 12/#6C757D vs web 11/gFaint css .g-mat-type step_collection_block.dart:303-306 11/gFaint
D8 Low Completion banner icon 14 vs web 16 + gap css:603 step_collection_block.dart:763 16
D9 Low Audio glyph — web 3-bar equalizer SVG vs Flutter soundwave _mat_thumb/_mat_gallery material_thumb.dart:126,132, guide_gallery_disclosure.dart:188,198 both audio semantics — cosmetic
D10 Low Gallery reveal strip missing top-pad .4rem + bottom 4 (22.3 M20 unfixed) css:256 guide_gallery_disclosure.dart:99-100 add strip pad
D11 Low .o-block-step-reset mt 12 vs web 11.2 css:147 step_block.dart:511 11.2
D12 Low Cook section-title icon gap 8 vs web ~12 css guide_lightbox_materials.dart:56, guide_lightbox_cook_mode.dart:117 ~12

Overview/footer/lesson cosmetic diverges (all LOW, all in shipped 22.5 steps 2/3): - O-D1 scroll-hint color legacy #999 vs gFaint (design_tokens.dart:936). - O-D3 Start button text 14 vs web 16px + arrow. - O-D4 objective "What you'll learn" w600 vs web <strong>=700. - FB-D1 learn progress-label TextAlign.center vs web left (footer.dart:191) — most visible on the overview full-width bar. - FB-D2 review "Exit Review" button = brand-colored default size vs web gray btn-sm btn-outline-secondary (footer.dart:219-227).


C. SYSTEMATIC / recurring items → each is ONE decision (not N scattered fixes)

  • C-1 — Anonymous disabled-button treatment. Flutter renders anon-disabled action buttons as a light-gray FILL + keeps the icon; web uses a transparent OUTLINE + drops the icon + "Log in" text. Appears on the overview Start button (O-D2) AND the lesson Mark-Complete button (DIVERGE-L1). Net tap behavior is identical (inert). Both smokes were authed-only → never A/B'd. → Decide once: match web (outline/text) or keep Flutter (gray-fill+icon) as an intentional lead.
  • C-2 — Progress-fill color: green vs blue→green. Flutter fills progress bars always green (gOk); web fills blue until 100%, green at 100% on the cook step + the collection block + the me-guides page. The learn-mode audit claimed "web makes cook+learn both green," but the functional agent found web COOK is still blue-until-done. → Reconcile: is Flutter's always-green an intended de-rainbow (then propose to web / log as a lead), or a divergence to correct? Affects cook footer, collection block, me-guides (P-BUG1).

D. UN-BUILT / shipped-but-unmigrated GAPS (the biggest scope items)

  • D-1 — Review page (Phase-22.5 STEP 4, not built). The whole surface is on legacy tokens: grey disc (not brand), indigo explanation box, inline full-width media (not MaterialThumb), #DEE2E6 borders. No structural gap (Flutter renders every web element). Full ordered build checklist + 36-token retirement list are in § Step-4 build checklist below. Two current-page bugs get fixed by the rebuild: rows left-align on wide screens (650 maxWidth, no Center); explanation box shows for flashcards (no flashcard exclusion, unlike the lesson page).
  • D-2 — Guide picker still on rainbow badges (SHIPPED, LIVE TODAY). Opens whenever a post has 2+ guides. Web is de-rainbowed to neutral g-*; Flutter (guide_picker.dart) still renders rainbow badges per type + Bootstrap greys, 1 badge vs web's 2 (ldjsonType + guideType), badge text = guideType vs web's ldjsonType, different meta line + modal metrics. This is the single biggest shipped-but-unmigrated style gap. De-rainbow retires 8 guidePickerBadge* tokens (:1024-1031).
  • D-3 — me-guides: reference is AMBIGUOUS → product decision. The web me-guides page exists but in profile-admin (guides.gohtml), NOT kontent, and is itself PRE-G (Bootstrap greys + old #1565c0; UTS never touched it). Flutter's me_guides_page is a reasonable port (data layer = full match), but 11/13 visual facets diverge. Decide before 22.6: match the pre-g web (stay pre-g) or do a joint web+Flutter g-* migration of a surface UTS never migrated.
  • D-4 — Entry-point a11y (minor). Flutter's hero compass HeroIconButton passes no tooltip / semantic label; web has title="Open guide"/"View N guides". Cosmetic on mobile (no hover) but the a11y label + count hint are absent. Everything else at the entry point is parity (compass icon, launch, deep-link).

E. Test-coverage & doc-accuracy corrections

  • GAP-T1 (important): No footer widget test and no completion widget test exist. This refutes the review doc's step-1 claim that free-nav is "exercised by the footer widget test" and completion honesty "by the completion widget." The nav-visibility matrix and the honest-completion branch — the two highest-risk behavior surfaces — are not regression-locked; only resumePage (bloc, 10 refs) covers resume indirectly. → Add a footer widget test (nav visibility per page-type + review no-bar) and a completion widget test (partial vs complete branch). Correct the step-1 claim.
  • Auth test gap: no test drives the Timer.periodic firing (no fakeAsync); the shared single-flight between the scheduler and the reactive-401 path lives in an un-unit-tested main() closure.
  • V1 correction: the memory/punchlist note "operator fixed the SAME disc-correctness bug on web too" is unsupported — web derived the disc from the server is_correct from inception (guide-lightbox.js:349-361,532-559; string-stable since 5825aeda). Web never had the id-compare bug; only Flutter did (fixed in 22.4). → Close punchlist V1 as "no web fix needed."
  • Completion score plan-target correction: phase-22.5-learn-parity-audit.md:53 says the score should be a "green-bold 24px gOk number." That target is a misread of DEAD CSS (.o-…-completion-score b is never emitted; the JS writes textContent). The real web render is flat 13px muted — which is what Flutter already does. Flutter is correct; the plan doc was wrong. Confirm flat-muted is intended.

F. DEAD code (bidirectional)

  • DEAD-not-retired (genuine misses, 0 external refs): guideLightboxBodyPaddingTop (design_tokens.dart:839) + guideLightboxBodyPaddingTopMobile (:842) — orphaned by the GuideBand masthead migration (cook now uses gBandMarginBottom, overview uses only the H tokens). Fall inside step-5's planned "final dead-const sweep" — flagged so they're not forgotten.
  • Confirmed retired (8/8, 0 live refs): canAdvance, guideBadge* (rainbow), guideLightboxStepBadge*, guideLightboxLessonBody*, guideLightboxLessonActionBorder, guideLightboxLessonDoneBorder, guideLightboxStepMediaMargin, guideLightboxProgressFillLearn.
  • To retire when Step 4 lands: 36 tokens (35 guideLightboxReview* :963-997 + guideLightboxStepMediaRadius :866).
  • To retire on picker de-rainbow: 8 guidePickerBadge* tokens (:1024-1031).
  • Web-side dead (out of Flutter scope): .o-guide-lightbox-lesson-action--done .btn (pale-green rule never applied by JS), the review *answer-label/pill CSS (JS builds innerHTML), the completion -score b green-bold rule. Flutter correctly does NOT replicate any of these. Retire on web at W12.

G. RENDER-QUESTIONS — need a live computed-style A/B (env is down)

These cannot be settled by a static source read (Step 3 proved the rendered web ≠ its source CSS). Owed to a live smoke pass (GoLand + flutter run -d macos + a playwright-cli A/B):

  1. C-1 anon-button render (overview Start + lesson Mark-Complete) — gray-fill vs outline.
  2. C-2 progress-fill cook/collection blue-until-100% vs Flutter always-green.
  3. D4 checkbox label — does the block-interaction JS rewrite "Mark as done"→"Done" on completion?
  4. Margin-collapse deltas (Flutter stacks, CSS collapses): band→About ~6px, About→objective ~4px, counter→title, title→media.
  5. <strong> "What you'll learn" rendered weight (600 vs 700).
  6. Completion score→actions gap (22.4 vs 28.4), Continue button neutralization.
  7. Review page: 650-row centering + first/last-child padding + flashcard disc red (web) vs green (Flutter) (§ latent web bug).
  8. Cook checkbox pixel size (Material SizedBox(20) vs input 16×16).
  9. Mobile footer safe-area (web env(safe-area-inset-bottom) on footer vs Flutter shell SafeArea).
  10. Review "Exit Review" rendered color; standalone progress-label class-swap; completion-banner injected text.

H. Owed runtime / API checks (not static-resolvable)

  • V3 residual: the api/v1 guide query loads WithCreatedBy() (guide_read.go:69) but no nested author-avatar/thumbnail load was found (cover has WithThumbnail(), author does not). If the masthead medallion needs an avatar image, verify the serialized created_by carries an avatar URL — a live JSON check.
  • V5: does web actually show a reset button to an anon who only revealed a cook-mode flashcard? Flutter gates it on auth (safer). Confirm the web behavior.
  • BUG-F1 severity — needs a page mixing a resettable block (with progress) + an answered assessment block.

Operator decisions needed (before further build)

  1. Anon-button treatment (C-1) — match web outline/text, or keep Flutter gray-fill+icon (→ W12 lead)?
  2. Progress-fill color (C-2) — Flutter always-green intended (→ propose to web), or correct to blue→green?
  3. me-guides reference (D-3) — stay pre-g to match current web, or joint g-* migration?
  4. Flashcard review disc (Step 4) — match web red (revealed flashcard reads "wrong"), or keep Flutter honest green? (Looks like a latent web bug.)
  5. Live A/B pass — bring the env up now to close the ~20 render-questions, or defer to the Step-4 smoke?

Step-4 (review page) build checklist — ready to execute

  1. Replace _buildNumberCircle (61 lines) with StepDisc(content:number, number:n, showStateMark:false) — brand default, size 30, 13/600, no rings (D22.5.c); state map incl. the flashcard red-vs-green decision.
  2. Restructure container/rows — outer padding: all(24); each row symmetric(vertical:16) + Center at maxWidth 650 + bottom border gLine on the 650 row; first row top:0; last row no border.
  3. Title 14/w600/gInk; body 13.5/1.6/gBody; body indent gDisc + 9.6.
  4. Primary media → MaterialThumb + primaryMediaTap (copy lesson_page.dart:203-218); drop inline MediaBlock.
  5. Answer block — KEEP pills + labels (D22.5.a) but migrate tokens → gOptCorrectBg/gOptIncorrectBg/gFaint uppercase labels.
  6. Explanation → shared ExplanationBox (D22.5.b KEEP); decide flashcard exclusion (REV-BUG3).
  7. Retire the 36 dead tokens.
  8. analyze + tests + live A/B smoke on Mixology (resolve the render-questions).

Provenance

9 agents, 2026-07-03. Per-surface raw findings preserved in the session; this doc is the synthesis. DoD for a clean re-run = the shipped-work fixes (§A/§B/§C) land + a live A/B closes §G + the review page (§D-1) is built + the dead set (§F) is retired.


UTS Phase 22 — Cross-Reference: Phases × Web × Current Flutter

Purpose: one place that answers "what's the difference, and whose job (which sub-phase) is it?" Built from grounded sources, not memory: - Web (the target / authoritative reference): static/css/kon_guide_v2.css + _guide_lightbox.gohtml + _block_*.gohtml, live on https://kon.test:8443 (dev main) and prod. Captured via playwright-cli (session uts-smoke) this session. - Current Flutter: the pre-UTS "Guides Phase 5" build + 22.2 SHIPPED (the phase-D clean rebuild, committed ec30c714 2026-06-29). Captured via marionette MCP (running macOS app) + source inventory. - Phases / scope: docs/plans/under_the_tuscan_sun/phase-22*.md (+ design-system-spec.md §5A–F/§7).

⚠️ Read this first. The A→B→C→D plan is complete for 22.2: the B-spike was reverted (C) and rebuilt cleanly (D), committed ec30c714 2026-06-29 (the spike stash has been dropped). So the Flutter app RIGHT NOW = pre-UTS baseline + shipped 22.2 atoms. Two states still matter for reading the matrix: 1. PRE-UTS baseline — what Flutter was before any UTS work (the "Guides Phase 5" build); still under the 22.2 atoms wherever they're not yet adopted (the whole lightbox shell, the post-body block layout). 2. TARGET — web parity, delivered across 22.1–22.6.

Most of the "huge differences" you're seeing are simply later phases not done yet (22.3 adoption, 22.4/22.5 lightbox) — NOT bugs and NOT scope creep. The matrix's "Flutter NOW" column = post-22.2-ship state.


1. The phase spine — what each sub-phase OWNS (the scope map)

Phase 22 is a flat series of 6 sub-phases (phase-22-flutter-parity.md:236-246). The overview, 22.1, 22.2, and 22.3 are authored (22.3 status Ready, assumption-audited); 22.4–22.6 are sequenced but not yet written.

Phase Owns (in scope) Doc authored? Build status
22 (overview) Program frame: Track A visual / Track B behavior / Track C architecture. Locks D22.1–D22.5. phase-22-flutter-parity.md n/a (frame)
22.1 — tokens Port the 17 :root g-* tokens (kon_guide_v2.css:15-40) → DesignTokens; brand-derived via app convention (const + colorScheme.primary + 2 Color.lerp). Additive (old guide* consts stay). phase-22.1-flutter-token-foundation.md built (ddd2bb7b token port; 22.2 adds the component tokens on top)
22.2 — shared components 7 atoms/molecules: A GuideGalleryDisclosure, B MaterialThumb, C GuideSection, D StepDisc, E de-rainbow labels (mat-type/optional/qtype/step-type/duration), F de-icon tip/explanation, G verify block chrome. phase-22.2-flutter-shared-components.md SHIPPED (D rebuild, ec30c714; verified web↔Flutter)
22.3 — post-body blocks ADOPT the 22.2 atoms into the 4 blocks: step_block, material_item_block, step_collection_block, material_collection_block. Repoint qty color (blue→gInk) + move trailing. Replace TimelineCircle→StepDisc. Wire GuideSection groups + MaterialThumb (opens primary); keep the gallery disclosure (DEC-22.3.3 parity). Verify §5C chrome. phase-22.3-flutter-post-body-blocks.md (Ready) not started
22.4 — lightbox shell + cook Opaque scrim, g-band brand masthead (cover, medallion, eyebrow, ultralight title, stats), g-md metadata dispatcher, cook content (reuse 22.2 atoms), footer progress. ❌ not authored not started
22.5 — lightbox learn + behavior (D22.1) Overview + adaptive CTA, lesson pages, honest completion/partial page, review mode, the nonsequential flip (un-gate canAdvance, stop auto-advance, omit score on partial, adaptive resume). ❌ not authored not started
22.6 — picker + cleanup Guide picker, me-guides page, delete dead old DesignTokens.guide* consts, on-device + web-reference smoke. ❌ not authored not started

The three tracks (phase-22-flutter-parity.md:70-73): - A — Visual (the g-* restyle): spread across 22.1→22.5. - B — Behavior (the learn-flow nonsequential flip, porting the Phase-20 7-point contract): concentrated in 22.5. - C — Architecture (guide-progress vs block-interaction independence): already parity in Flutter, no work.


2. Surface-by-surface matrix (Web target vs Flutter now vs owning phase)

Legend for Flutter now: ✅ matches web · 🟡 partial · ❌ pre-UTS/missing · ⚠️ gap to fix.

2A. Post-body content blocks (live-validated both platforms this session)

Surface / element Web (target) Flutter NOW (B spike) Owning phase In scope for 22.2?
Tip callout .g-tip amber bg/border, ink #7a5b00, 13px, no icon ✅ exact (#7A5B00, 13px, de-iconed) 22.2-F YES — done
Explanation .g-explain #3a3a3a 13px, brand #cf0015 left border, tint bg, no icon ✅ exact 22.2-F YES — done
Material type label .g-mat-type "· ingredient" — gFaint #9aa0a6 11px, middot prefix ✅ "· ingredient" (middot D-fix shipped) 22.2-E YES — done
Optional badge .g-mat-opt bordered pill, gFaint 10px, ls .3, uppercase ✅ exact 22.2-E YES — done
Question sub-type .g-qtype "multiple choice" gFaint 11px lowercase, no icon ✅ exact 22.2-E YES — done
Step type badge removed (disc glyph carries type) ✅ removed (no INSTRUCTION/QUESTION pill) 22.2-E YES — done
Duration .g-step-dur "15 min" gMuted #6b7280 10px, tint pill, no clock ✅ exact 22.2-E YES — done
Gallery "Show N" .g-gal <details>; summary dark-red gBrandInk; 64px tiles; type-aware ✅ exact ("Show N" #810F1B, 64px, 6px gap, expand/collapse) 22.2-A YES — done
Material qty .g-mat-qty dark (gInk) blue #1565C0 (old guideMaterialQuantityColor) 22.3 NO — deferred
Material thumb .g-mat-thumb 48px type-aware square (image/video/audio/doc) + count badge ❌ old layout; MaterialThumb created-not-adopted 22.3 NO — deferred (atom built, not wired)
Material groups .g-acc "Dough −"/"Equipment −" open-by-default accordions ❌ flat list; GuideSection created-not-adopted 22.3 NO — deferred
Step disc 30px glyph-by-type (bars/?/N), state colors ❌ old TimelineCircle (24px, halo ring) still used 22.3 NO — deferred (StepDisc built, not wired)
Block chrome §5C (heading/divider/hidden) dispatcher-owned 🟡 believed already correct (content_block_renderer.dart) — verify-only 22.2-G / 22.3 verify

2B. Guide lightbox (web cook masthead live this session; Flutter from source inventory — entirely pre-UTS)

Surface Web (target) Flutter NOW Owning phase
Scrim opaque #000 🟡 modal exists; scrim style pre-UTS 22.4
Masthead .g-band full-bleed cover + brand band (#cf0015) + author medallion + eyebrow "FOOD · RECIPE" + ultralight (w200) title + stat line "Intermediate · 24 hrs · 1 loaf" no brand-band masthead; overview = plain cover + title + badges + stats (guide_lightbox_overview.dart:73-102) 22.4
Metadata .g-md dispatcher (URL→link, ISO-8601→"N min", humanized keys) ❌ pre-UTS metadata grid 22.4
Cook mode materials + steps single scroll, footer "Step N of 9" 🟡 exists (guide_lightbox_cook_mode.dart), pre-UTS styling 22.4
Learn overview brand band + adaptive Start/Continue/Review CTA 🟡 exists, pre-UTS, CTA logic present (resumePage) 22.5
Lesson pages paged, neutral labels, disc glyphs 🟡 exists (guide_lightbox_lesson_page.dart), pre-UTS styling 22.5
Completion / partial honest partial end-page; score omitted on partial 🟡 completion page exists; partial-honesty = Track B port 22.5
Review mode read-only, color-coded discs 🟡 exists (guide_lightbox_review.dart), pre-UTS styling 22.5
Flashcard .g-flash front/back reveal 🟡 FlashcardPanel adopted (6 sites), styling pre-UTS in lightbox 22.2/22.5
Picker "Choose a Guide" modal 🟡 exists (guide_picker.dart), pre-UTS 22.6
Footer progress bar + label; learn Prev/Next 🟡 exists (guide_lightbox_footer.dart); Next gated (see 2C) 22.4/22.5

2C. Behavior — Track B (the one NON-visual difference; the big behavioral gap)

Behavior Web (target, post-Phase-20) Flutter NOW Owning phase
Learn-mode navigation NONSEQUENTIAL — free Prev/Next, never gated SEQUENTIALcanAdvance gates Next on current-step completion (guide_progress_state.dart:82-90) 22.5 (D22.1)
Auto-advance on complete no ❌ auto-advances 22.5
Completion page honest partial ("X of N") + omit score on partial 🟡 has completion + score; partial-honesty not ported 22.5
Resume CTA adaptive Start/Continue/Review 🟡 resumePage computed client-side already (guide_progress_state.dart:92-100) 22.5
Review-return-to-origin yes 🟡 review flow exists 22.5
Guide-progress ↔ block-interaction independence (Track C) independent already independent (separate blocs/repos/endpoints /progress/guide/* vs /progress/block/*) none — parity

3. Widget adoption ledger (current Flutter, grounded file:line)

Widget File Status Notes
GuideGalleryDisclosure guides/ui/molecules/guide_gallery_disclosure.dart:18 ADOPTED (9 sites) replaces deleted guide_thumbnail_strip.dart (22.2-A)
TipBox / ExplanationBox guides/ui/atoms/{tip_box,explanation_box}.dart ADOPTED (B restyle) de-iconed (22.2-F)
StepBadgeRow / MaterialTypeBadge guides/ui/atoms/step_badge.dart, material_type_badge.dart ADOPTED (shipped) de-rainbowed (22.2-E); ✅ middot D-fix shipped
GuideMediaTile (Glyph/Play/Tag) guides/ui/atoms/guide_media_tile.dart ADOPTED shared by disclosure + thumb (22.2 spike, 4th file = DEC-2)
MaterialThumb guides/ui/molecules/material_thumb.dart:18 CREATED-NOT-ADOPTED wire in 22.3
GuideSection guides/ui/molecules/guide_section.dart:11 CREATED-NOT-ADOPTED wire in 22.3
StepDisc guides/ui/atoms/step_disc.dart:18 CREATED-NOT-ADOPTED replaces TimelineCircle in 22.3
TimelineCircle guides/ui/atoms/timeline_circle.dart:15 OLD-STILL-USED (step_collection_block.dart:209,395) retire in 22.3
MaterialRow guides/ui/molecules/material_row.dart ADOPTED (3 sites) uses gallery disclosure; does not yet use MaterialThumb → 22.3
FlashcardPanel / AnswerOptionRow guides/ui/molecules/ ADOPTED lightbox styling still pre-UTS (22.5)

4. What "in scope right now" means (the answer to the scope question)

22.2 is SHIPPED (ec30c714, 2026-06-29). The next work item is 22.3. What 22.2 delivered (the 7 atoms/molecules in §1): - Done & verified (web-matched, A/B'd live): gallery disclosure, de-iconed tip/explanation, de-rainbowed mat-type/optional/qtype/step-type/duration labels, the "· " middot D-fix. (See §2A.) - Built but intentionally not wired (their adoption is 22.3, not 22.2): MaterialThumb, GuideSection, StepDisc. They exist in the tree but nothing renders them yet — that is correct, not a miss.

Explicitly OUT of 22.2 scope (so not gaps — just later phases): - Material qty color, thumb layout, accordion groups, step disc swap22.3. - The entire lightbox look (masthead/metadata/cook/learn/review/picker) → 22.4 + 22.5. - The nonsequential learn-flow behavior (canAdvance un-gate + honest partial page) → 22.5 (D22.1). - Deleting old DesignTokens.guide*22.6.

Out of the whole Phase-22 / UTS program: - Standalone media / media_collection blocks (under_the_tuscan_sun.md:39). - Studio guide-cover picker (web-only, shipped Phase 19; Flutter just reads guide.cover_image).


5. Evidence basis (this session)

  • playwright-cli uts-smoke (web, dev sourdough post 46): computed styles for .g-tip/.g-qtype/.g-step-dur/ .g-explain/.g-mat-opt/.g-mat-type (all de-iconed, exact values), --kon-brand-color = #cf0015, "Show N" element screenshot (dark-red), material-row screenshot (middot present), and the cook lightbox masthead screenshot (g-band: cover + brand band + medallion + eyebrow + ultralight title + stats + "Step 0 of 9").
  • marionette MCP (Flutter macOS app, same post 46): exact rendered colors/sizes for all 7 widget groups (TipBox, GalleryDisclosure, MaterialTypeBadge, OptionalBadge, StepBadgeRow duration + qtype, ExplanationBox), brand rgb(207,0,21); confirmed flat (ungrouped) material collection + blue qty + no middot.
  • Source inventories (2 grounded Explore passes): full widget adoption ledger + canAdvance sequential gate + lightbox surface map + per-phase scope with doc:line.

Method note: read static computed styles with eval(getComputedStyle); used snapshot+screenshot (not eval) for anything stateful/nav, per operator guidance. See feedback_test_via_real_ui_not_eval.

← Back to Under the Tuscan Sun