Phase 22.4 — The Second Kitchen
05 JUL AT 08:38 PM

Phase 22.4 — The Second Kitchen

0 LOVES 0 VIEWS
Flutter lightbox shell + cook.

Under the Tuscan Sun — Phase 22.4: Flutter guide-lightbox SHELL + COOK content (parity)

Gated on 22.1 (g- tokens), 22.2 (shared atoms: StepDisc/MaterialThumb/GuideGalleryDisclosure/GuideSection/AnswerOptionRow/FlashcardPanel/TipBox/ExplanationBox/StepBadgeRow), 22.3 (those atoms adopted + proven in the 4 post-body blocks, committed 83d32232). Flutter mirror of web Phases 9 + 10 (phase-9-guide-shell.md shell/masthead/metadata · phase-10-lightbox-cook.md cook content). Web is the reference; web is on prod. Restyle-led, reuse-first — the cook content reuses the SAME g- atoms 22.3 shipped. Baby-step build, smoke each surface side-by-side vs web (playwright uts-smoke + marionette). ⚠️ Intermediate MIXED state is expected + intentional — 22.4 restyles the shared SHELL + COOK content; the LEARN content (overview CTA / lesson pages / completion / review / nonsequential behavior) stays on old guideLightbox* styling until 22.5. The shared masthead/metadata/materials WILL update the learn overview header too (they're shared widgets), but the learn-specific chrome + behavior is 22.5.

Goal

Bring the Flutter guide lightbox's shared shell + cook-mode content to parity with the web reference: - Shell — opaque #000 field + centered white ≤650 panel (full-bleed on mobile); the close button. - Masthead — full-bleed natural-aspect cover + brand .g-band (author medallion · eyebrow · ultralight centered title · stat line). Badges FOLD into the stat line — no separate badge row. - Metadata — the generic .g-md value-syntax renderer (URL→brand-ink link · ISO-8601→humanized · else key:value), skip visible=0. - Materials — flat g-grouphdr + hairline g-mat rows, no outer card (reuse MaterialRow + shared thumb/gallery). - Cook steps — reuse StepDisc (ordinal, brand/ok/bad) + de-rainbowed head (neutral StepBadgeRow qtype + tint dur pill) + the shared answer/flashcard/tip/explanation atoms. - Cook footer — progress gOk, reset brand-ink. - Duration — humanize (estimatedTime int → "2 hrs"; metadata ISO PT30M → "30 min").

Look/feel only. The lightbox behavior — open/close, cook interactions (mark-as-done / MC-TF submit / flashcard reveal), progress, hydrate-on-open + sync-back-on-close — is the existing GuideProgressBloc (guide_lightbox.dart:62-104). Preserve it; do not change behavior.

Scope (shell ÷ content split)

  • IN (22.4): the shell (guide_lightbox.dart — barrier + panel + close), the shared masthead + metadata (guide_lightbox_cook_mode.dart _buildCoverImage/title/_buildBadges/_buildStats/_buildMetadataGrid — factored into shared GuideBand + GuideMetadata widgets used by cook AND the learn overview), the cook Materials (guide_lightbox_materials.dart), the cook Steps (guide_lightbox_step.dart) + the steps section header, the cook footer progress + reset (guide_lightbox_footer.dart — cook branch), + the duration humanizers.
  • OUT → 22.5: the learn-specific content — guide_lightbox_overview.dart About/objective callouts + adaptive Start/Continue/Review CTA + scroll hint; guide_lightbox_lesson_page.dart; guide_lightbox_completion.dart; guide_lightbox_review.dart; the learn footer prev/next nav; the nonsequential behavior (Phase-20 contract). The guide_picker.dart multi-guide chooser also → 22.5.
  • OUT of program: standalone media/media_collection blocks, CEWL (as ever).

The model / behavior to preserve (do NOT change)

  • DispatchGuideProgressLoaded.mode == 'cook'GuideLightboxCookMode, else learn (guide_lightbox.dart:130-132).
  • ProgressGuideProgressBloc hydrated on open (GuideProgressHydrated), synced back to PostDetailBloc on close (_syncProgressBack, guide_lightbox.dart:85-104). Cook interactions dispatch to this bloc.
  • DataGuide (guide.dart): name, guideType, ldjsonType, difficulty, estimatedTime (int? min), servings (String?), outcome (String?), edges.coverImage (Media?), edges.metadata (List? — each visible/displayName/metaKey/metaValue), edges.materialCollection, edges.stepCollection.edges.items, edges.createdBy (User?).

1. Shell (scrim + panel) — guide_lightbox.dart

  • Web target: .o-guide-lightbox{ background:#000 } opaque field + .o-guide-lightbox-panel{ width:100%; max-width:650px; max-height:90vh; border-radius:.75rem; overflow:hidden }; mobile @≤767.98px → full-screen (max-width/height:100%; border-radius:0) (D9.1 reopened, kon_guide_v2.css:312-313,399).
  • Flutter now: showDialog(barrierColor: Colors.transparent, useSafeArea:false) (:37-38) → Material(color:white) + SafeArea + full-screen Stack (:118-121). No scrim, no panel.
  • Change: barrierColor: Colors.black (opaque). Wrap the mode body in a centered panel: desktop Center(child: ConstrainedBox(maxWidth:650, maxHeight: 0.9*screenH))Material(color:white, borderRadius:.75rem, clipBehavior:antiAlias); mobile (<768) → full-screen (no radius, fill). Close button anchored top-right of the panel. Keep ESC, keep the bloc wiring.

2. Masthead / band — GuideBand (new shared widget) replaces _buildCoverImage+title+_buildBadges+_buildStats

  • Web target: full-bleed .g-band-cover{ width:100%; height:auto } natural aspect, no crop, bled to the panel edge (width:calc(100%+3rem) desktop / +2rem mobile) (css:262,359); then .g-band{ background:var(--g-brand)[#cf0015]; color:var(--g-on-brand)[#fff]; text-align:center; padding:0 1rem 1.25rem; margin-bottom:1.1rem } (css:260-261) containing: .g-band-author{ 84px circle; margin:-42px auto .4rem } (avatar else initial) · .g-eyebrow uppercase = guide_type · ldjson_type deduped · .g-title{ font-weight:200; font-size:2rem; line-height:1.15 } · .g-band-stats{ font-size:12px; font-weight:200 } = capitalize(difficulty) · humanizeMinutes(time) · (servings else outcome), joined ·.
  • Flutter now: inset rounded cover fixed-height (_buildCoverImage :116-138), left-aligned dark title (:58-68), colored badge row (_buildBadges :140-157, guideType + difficulty), icon stat rows (_buildStats :159-220, clock/cup_hot/bullseye + raw ${estimatedTime} min). Duplicated in overview.dart:157-267.
  • Change: new GuideBand widget — full-bleed natural-aspect cover (bleed out of the panel padding), brand band (colorScheme.primary bg, white content, centered): medallion (edges.createdBy avatar else initial; 84px; overlaps cover seam −42px), eyebrow (type · ldjsonType deduped, uppercase, faint-on-brand), ultralight w200 2rem title, stat line (difficulty · humanized-time · servings-else-outcome, text only, no icons, no separate badge row). Use in cook_mode + overview.

3. Metadata — GuideMetadata (new shared widget) replaces _buildMetadataGrid

  • Web target: .g-md{ display:grid; grid-template-columns:auto 1fr; gap:.4rem 1rem; align-items:baseline } (css:287); .g-md-k{ font-size:11px; uppercase; color:var(--g-faint) } (:288) + .g-md-v{ font-size:13.5px; color:var(--g-body) } (:289); value dispatch: http…<a target=_blank> .g-md-v a{ color:var(--g-brand-ink) } (:290) · ISO-8601→formatISODuration · else plain. Skip visible=0. Label = displayName else humanizeKey (snake→spaced).
  • Flutter now: _buildMetadataGrid :222-263 — a Wrap of key/value columns; value rendered as raw Text(m.metaValue) (:250); no link, no ISO handling.
  • Change: GuideMetadata — a 2-col grid (or Table); key = uppercase 11/faint; value dispatched: http*→tappable brand-ink link (launchUrl externalApplication, reuse openMediaUrl/launchUrl), ISO PT…→humanized, else plain body text; skip visible=0; label = displayName else humanizeKey. Use in cook_mode + overview.

4. Materials — guide_lightbox_materials.dart

  • Web target: flat, no card — plain .o-guide-lightbox-section-title{ 11px/700 uppercase; color:var(--g-faint); border:none } (css:369), then .g-grouphdr{ 11px uppercase; color:var(--g-faint); padding:1.1rem 0 .35rem; border-bottom:1px solid var(--g-line) } (:49) + hairline .g-mat rows (:73, :last-child no border).
  • Flutter now: _buildSectionHeader (guideLightboxSection + border) then a bordered outer card Container(border+radius+clip) (:41-53); filled group headers (guideLightboxMaterialGroupBg, :125-149); each item in a Container with bottom + left-accent* border (:105-118) around a MaterialRow.
  • Change: delete the outer card, the filled group-header bg, the left-accent item borders. Render a plain faint uppercase section title, flat g-grouphdr group headers (faint uppercase + bottom hairline), and hairline MaterialRows (reuse the 22.3 MaterialRow as-is; it already carries the thumb + GuideGalleryDisclosure). isLast drops the bottom border. This is the same flatten as 22.3's MaterialCollectionBlock (which dropped its card) — the lightbox just kept its card.

5. Cook steps — guide_lightbox_step.dart + the steps section header (cook_mode.dart:265-311)

    • Web target: .g-step row + .g-step-num ordinal disc (brand; done→--g-ok ✓, correct→ok, incorrect→bad); .g-step-head = .g-step-title + neutral .g-qtype(questions only) + airy .g-step-dur{ tint pill, no icon }; .g-check mark-as-done; .g-tip; .g-opt/.g-submit/.g-explain/.g-flash. Section title = plain faint uppercase, no icon.
    • Flutter now: GuideLightboxStep — number circle on guideLightboxStepNumber*; rainbow badges (_buildStepBadges :228-274 = per-type colored badge + colored duration badge); old checkbox/answer styling. Section header has a list_ol icon + border (cook_mode.dart:273-300).
    • Change: reuse StepDisc (ordinal content, done/wrong states → brand/ok/bad); reuse StepBadgeRow (title + neutral qtype + tint dur pill — de-rainbow, drop the colored type + duration badges); reuse AnswerOptionRow / FlashcardPanel / TipBox / ExplanationBox / the mark-as-done checkbox styling from 22.3; section title → plain faint uppercase (drop the icon + heavy border). Wire to the existing GuideProgressBloc interactions (unchanged). NOTE: correctness must read the server verdict (the step response's isCorrect), not an id-compare — same bug class fixed in 22.3.
    • Web target: .o-guide-lightbox-footer{ background:#fcfbf9; border-top:1px solid var(--g-line) } (css:383); progress fill var(--g-ok) (:388); reset = var(--g-brand-ink) text button (:391-395).
    • Flutter now: old guideLightbox* tokens (guide_lightbox_footer.dart:46-253).
    • Change: progress fill gOk, track gLine2; reset = brand-ink text link (underline on press, like 22.3's reset links); footer bg #fcfbf9, top hairline gLine. (Learn nav parts of this file stay untouched → 22.5.)

Duration humanizers (new Dart util — port module.go)

    • humanizeMinutes(int) → "30 min" / "2 hrs" (masthead stat line; estimatedTime is int minutes, so a 1440-min guide must read "24 hrs" not "1440 min").
    • formatISODuration(String)PT30M→"30 min", PT1H30M→"1 hr 30 min", pass-through non-ISO (metadata values).
    • Server-side only on web (no JS to port) — reproduce in Dart (lib/features/guides/… util). Step-level ${durationMinutes} min already matches web (raw int + " min") — no change.

New tokens to add (design_tokens.dart)

Structural/sizing only — brand = colorScheme.primary, on-brand = Colors.white, and gBrandInk/gOk/gBad/gLine/gLine2/gFaint/gMuted/gTint/gInk/gBody already exist (22.1/22.3): - gBand* — band padding, cover full-bleed overhang (desktop 3rem / mobile 2rem), medallion size 84 + overlap −42, band margin-bottom 1.1rem. - gEyebrow (size/spacing), gTitle (2rem / w200 / lh 1.15), gBandStats (12 / w200). - gMd* — grid gaps (.4rem row / 1rem col), key (11 uppercase faint), value (13.5 body). - gGrouphdr (11 uppercase faint; padding 1.1rem 0 .35rem). - Panel: gLightboxPanelMaxWidth (650), gLightboxPanelRadius (.75rem = 12), gLightboxPanelMaxHeightFraction (0.9). Retire the old guideLightbox* tokens they replace as each surface lands (bidirectional cleanup, per the 22.3 sweep). Any surviving old tokens (learn) stay until 22.5.

Decisions (ported from web Phase 9/10 — operator-settled there; carry as parity)

    • D22.4.1 = shell. Opaque #000 + centered 650 panel (full-bleed mobile). (web D9.1-reopened)
    • D22.4.2 = masthead. Brand .g-band (medallion / eyebrow / ultralight title / stat line); badges fold into the stat line, no badge row. (web D9.1/D9.2)
    • D22.4.3 = metadata. Fully generic value-syntax dispatch (URL→link · ISO→humanized · else key:value); skip visible=0; no stars, no chips (ratings = text). (web D9.3)
    • D22.4.4 = author medallion. Keep (edges.createdBy avatar else initial). (web D9.4)
    • D22.4.5 = materials. Flat g-grouphdr, no card, no accordion (the lightbox is a focused task surface; the standalone block keeps its accordion). (web D10.1)
    • D22.4.6 = step badges. De-rainbow → neutral qtype + tint dur pill. (web D10.3)
    • D22.4.7 = step disc. Ordinal 1..N (not the instruction-glyph/?), done/correct→ok, incorrect→bad. (web D10.4)

⚠️ Assumptions to VERIFY before building (the 22.3 lesson — open the edit site, don't trust the summary)

    1. Author medallion data — the Guide model has edges.createdBy (User?), BUT does the guide API response for Flutter populate created_by (+ the user's avatar)? If the Go guide service/endpoint doesn't eager-load it for the Flutter path, the medallion is a backend gap (defer the medallion or add the eager-load). CHECK before building surface 2.
    2. User avatar field — confirm the Flutter User model exposes an avatar URL + a name/initial fallback for the medallion.
    3. Step correctness signal — confirm how GuideLightboxStep derives correct/incorrect today; it MUST use the server verdict, not an id-compare (the 22.3 disc bug). Read guide_lightbox_step.dart + the GuideProgressState/step-response model before surface 5.
    4. GuideMetadata link tap — confirm a URL-opening path (reuse 22.3's openMediaUrl/launchUrl externalApplication).
    5. Mobile panel height — 90vh via MediaQuery.sizeOf(context).height * 0.9; verify SafeArea interaction (the shell currently uses SafeArea).

Baby-step order (build → analyze/test → smoke each vs web, hold commits)

    1. Shell — barrier #000 + centered panel (reframes everything; smoke the frame first).
    2. Duration humanizers — small Dart util + unit tests (unblocks 3, 4).
    3. MastheadGuideBand shared widget (verify medallion data first). Biggest visual — smoke side-by-side.
    4. MetadataGuideMetadata shared widget (link + ISO dispatch).
    5. Materials — flatten (drop card/fills/left-borders).
    6. Cook steps — reuse the 22.3 atoms + de-rainbow + verdict-correctness.
    7. Cook footer — progress gOk + brand-ink reset. Then dead-token sweep for the retired guideLightbox* (cook/shell subset only — the learn ones stay for 22.5).

Gates

    • Per surface: flutter analyze clean (only the pre-existing share_plus deprecations) + guide/lightbox tests green + operator visual smoke side-by-side vs web (uts-smoke playwright + marionette on the rebuilt app), on a cook guide with cover + metadata (URL + ISO) + grouped materials + a question step + a flashcard (Sourdough / Mixology on dev main).
    • Behavior intact: open/close/ESC, mark-as-done, MC/TF submit (verdict-correct), flashcard reveal, progress count, hydrate + sync-back.

Depends-on / notes

    • 22.1 tokens, 22.2 atoms, 22.3 atom-adoption (all committed). No backend change expected EXCEPT possibly eager-loading created_by for the guide endpoint (assumption 1).
    • The nested viewer (tap a material/step media thumb → gallery/media lightbox above the guide lightbox) already works via 22.3's primaryMediaTap (guide_media_launch.dart); verify it opens above the panel.
    • 22.5 = learn (overview adaptive CTA · lesson pages · completion · review · the nonsequential flip · picker) + retire the remaining guideLightbox* + retire the old lightbox learn tokens.

Follow-ups / remaining (after the 7 cook surfaces — tracked, NOT lost)

Surfaces 1–7 are built + analyze-clean + tests green + smoked. The items below are deliberately deferred (either 22.5 scope, a backend dependency, or a cleanup pass):

    1. Learn-mode masthead/metadata reuse (22.5). guide_lightbox_overview.dart (learn mode's landing) still renders the OLD cover/title/badge/stat code + its own metadata grid. Adopt the new GuideBand + GuideMetadataGrid there when 22.5 restyles learn. Until then learn mode keeps the legacy look.
    2. ✅ DONE (2026-07-02) — step primary media → 48dp o_mat_thumb tile. Web renders a step's primary media via {{ template "o_mat_thumb" $step }} inside .g-step-media — a 48dp type-aware thumbnail tile (image / video+play / audio+waveform / doc glyph, gallery-count badge) that taps into the shared viewer (image→gallery, video/audio→media lightbox), NOT an inline player. Flutter was embedding MediaBlock inline (full-width, plays in place). Fixed guide_lightbox_step.dart: dropped the MediaBlock import + inline ClipRRect, now renders Align(left, MaterialThumb(media, galleryCount, onTap: primaryMediaTap(context, media))) (top gap gStepMediaMarginTop = .55rem). Reuses the 22.3 material thumb + launch helper — the flashcard path already used them. Smoked live: step-3 video thumb + step-4 audio thumb render at 48dp; tapping the video thumb opened the shared media lightbox ABOVE the guide panel and closed back cleanly.
    3. Dead-token sweep — retired cook/shell guideLightbox* tokens. Now-unreferenced after 22.4: guideLightboxCover*, guideLightboxTitle*, guideLightboxBadge*, guideLightboxStats*/stat, the old guideLightboxMeta* grid tokens, guideLightboxSection*, guideLightboxMaterialGroup*/MaterialItem*, guideLightboxStepNumber*, guideLightboxStepBadge*, guideLightboxStepMediaMargin/guideLightboxStepMediaRadius (step now uses MaterialThumb + gStepMediaMarginTop), and guideBadge* (post-body); plus guideLightboxResetColor/guideLightboxResetHoverColor (footer reset now uses brand-ink + the shared g-modal). Verify each is truly unreferenced (grep both lightbox + post-body) before deleting — some are shared with learn mode and must stay until 22.5. Do this as one measured sweep (the 22.3 method: grep the FULL list, delete only the zero-reference ones, keep the learn-shared ones).
    4. Learn mode full restyle + behavior = Phase 22.5. Nav footer buttons (_navButton), learn progress fill (still blue guideLightboxProgressFillLearn; web is --g-ok green — flip in 22.5), review footer, overview adaptive CTA, lesson pages, completion summary, the nonsequential flip, the lesson picker. All untouched here.
    5. 🔴 WEB-SIDE GAP flagged back to web team — guide-lightbox reset modal not g-styled. The web post-body block reset modal IS g-styled (⚠️ icon badge + red callout box, kon_guide_v2.css:154 scoped via :has(.o-block-reset-modal-body)), but the web guide-lightbox reset modal (guide-lightbox.js showResetModal) is still a plain Bootstrap modal-sm (bare .modal-body, no icon/callout) — looks like an un-migrated UTS gap, not an intentional choice. Operator decision (2026-07-02): Flutter KEEPS the g-styled card (reused 22.3's showResetConfirmDialog + a new guide scope) so both Flutter reset modals are consistent; web should g-style its guide reset modal to match (add the icon badge + .o-block-reset-modal-warning callout treatment to #guide-reset-modal). Until web does that, Flutter intentionally leads web on this one modal (the only accepted 22.4 divergence).

UTS Phase 22.4 — Cook-Lightbox Flutter↔Web Parity Audit (delta)

Date: 2026-07-02 · Scope: the guide-lightbox COOK mode only (learn mode = Phase 22.5, explicitly excluded). Method: 7 read-only agents, one per surface, each enumerated EVERY web element/CSS-property/JS-behavior from the authoritative source (static/css/kon_guide_v2.css, internal/http/web/kontent/v1/templates/partials/organisms/_guide_lightbox.gohtml, static/js/kontent/utils/guide-lightbox.js, handlers/module.go) and checked the corresponding Flutter value (lib/shared/widgets/organisms/guide_lightbox/*, lib/features/guides/ui/*, lib/config/design_tokens.dart). Bidirectional: MATCH / DIVERGE / DEAD (Flutter-only) / MISSING (web-only).

Measured totals: ~251 MATCH · ~52 DIVERGE · ~18 DEAD tokens · 1 ACCEPTED-divergence. Confidence the cook delta is now enumerated: ~93%. Basis: all 7 surfaces' web source read in full and checked property-by-property against Flutter source; residual ~7% = the COULD-NOT-VERIFY set below (a server-side redaction question + a few runtime-pixel confirmations) + learn mode deferred to 22.5. Nothing here is fixed yet.

Per-surface confidence (all agent-reported): shell/masthead ~high (58/1), metadata ~93%, materials (34/11), step-layout (24/12), step-interactions ~93% (51/13), footer ~93% (30/2), behavior ~93% (21/8).


TIER 1 — Functional / behavioral (correctness, fix first)

# Finding Web (source) Flutter (source) Fix
1 🔴 Reset → close → reopen shows stale "completed" steps (real state bug) reset updates in-memory cache; reopen hydrates cleared (guide-lightbox.js:687,67-73) _syncProgressBack early-returns when completedItemIds.isEmpty && responses.isEmpty (guide_lightbox.dart:91) → a reset is never written back to PostDetailBloc; reopen reads pre-reset guideProgress[id] Drop the isEmpty guard, or force sync-back when a reset happened this session
2 Disc shows ✓/✗ glyph instead of the ordinal number (verified 3 ways in source) disc text is always {{ $stepNum }} (tmpl:291); JS only toggles classes (js:265-268,306-307,356-363); CSS :424-426 sets only background green/red — no ::after/content StepDisc done→check / wrong→x glyph replaces the number (guide_lightbox_step.dart:70-92 + step_disc.dart:60-61) Lightbox disc: keep ordinal + change bg color only. ⚠️ StepDisc is SHARED — the standalone post-body block DOES want ✓/✗; scope the fix to the lightbox caller (variant flag), do NOT change the atom globally
3 Anonymous flashcard reveal is inert (regression) reveal is NOT auth-gated — anon users SEE the answer; only the POST is if(loggedIn) (js:414-418,587-612) onReveal: !isAuthenticated ? null (guide_lightbox_step.dart:336) → anon button does nothing Allow local reveal for anon; skip only the repository call
4 Backdrop / outside-panel tap closes the lightbox (extra close path) no backdrop handler; only X + ESC close (js:110-113,83-88) showDialog default barrierDismissible:true (guide_lightbox.dart:35-40) → tapping the black margin (tablet/desktop) closes it barrierDismissible: false (one line)
5 ✅ FIXED (Flutter-side) — visible=false metadata row reappeared server hides via {{ if .Visible }} (tmpl:419); backend emits json:"visible,omitempty" (ent/guidemetadata.go:27) — a DELIBERATE lean-payload choice, web never reads it Flutter model was @Default(true) → an ABSENT field (which omitempty only ever produces for false) wrongly became true → hidden row shows NOT a backend change (web works, needs nothing). omitempty guarantees present⇒true / absent⇒false, so the model default must be false: guide_metadata.dart@Default(false) bool visible + build_runner regen (?? false). Test updated to assert the contract.
6 Hydrated incorrect answer highlights the correct option green (Flutter richer — mismatch, not clearly a bug) hydration stays neutral on a past-incorrect answer (js:335-338) builds correctOptions from answerOptions.isCorrect for answered items (guide_progress_bloc.dart:77-91) DECISION NEEDED: accept Flutter's richer behavior, or suppress correct-highlight on hydrated-incorrect for parity. Depends on server un-redacting is_correct (see COULD-NOT-VERIFY)

TIER 2 — Visible content / text / structure misses

# Finding Web Flutter Fix
7 Materials heading = collection title, should be fixed "Materials" (operator-spotted) literal "Materials" (tmpl:252) sectionTitle ?? collection.title ?? 'Materials' → collection title in cook (guide_lightbox_materials.dart:33) sectionTitle ?? 'Materials' (drop the collection.title fallback)
8 Section-title icons dropped (Materials bi-box-seam, Steps bi-list-ol) <i class="bi bi-box-seam">Materials, <i class="bi bi-list-ol">Steps; .o-guide-lightbox-section-title flex + gap:.5rem (tmpl:252,278; css:369) plain uppercase text, no icon (materials.dart:45-53, cook_mode.dart:108-116) Add faint leading Icon (box_seam / list_ol) in a Row, gap 8px. (I de-iconed these this session — that was wrong)
9 Submit button — left-aligned, wrong label, unstyled .g-submit centered (width:fit-content;margin:1.1rem auto 0), brand bg, 13/w600/radius6/pad .5×1.2rem (css:134,138), label "Submit Answer" (tmpl:346) left-aligned bare ElevatedButton, no styleFrom, label "Submit", top:8 (guide_lightbox_step.dart:271-278) Center it; label "Submit Answer"; apply brand gSubmit* styling (the standalone step_block.dart:387-403 already does it right); margin-top 17.6
10 Answer option missing trailing ✓/✗ mark + leading-icon color differs correct ::after{content:"✓";margin-left:auto;color:g-ok;700;1rem}, incorrect ::after "✗" g-bad (css:577-578); leading .bi stays faint (css:440) Row [Icon,gap,Expanded(Text)], no trailing mark; colors the LEADING icon green/red (answer_option_row.dart:48-100) Add the trailing bold ✓/✗ (right-aligned, g-ok/g-bad); keep leading icon faint. (Flutter's colored-leading is defensible — DECISION)
11 Completed-instruction title strikes in the lightbox strike rule :111 is scoped to .o-block-step--completed (standalone block ONLY); lightbox gets .o-guide-lightbox-step--completed with no strike rule completed && instructionlineThrough+gMuted (guide_lightbox_step.dart:109-110, step_badge.dart:71-72) Don't strike completed titles in the lightbox
12 Mark-as-done checkbox fills green, not brand (white-label miss) accent-color:var(--g-brand) (css:106) activeColor: guideLightboxCheckboxAccent=#198754 (guide_lightbox_step.dart:218) Use colorScheme.primary. guideLightboxCheckboxAccent is a legacy holdover
13 Last step draws an extra bottom hairline .g-step:last-child border-bottom:none (css:90) every step gets Border(bottom:gLine) (guide_lightbox_step.dart:82-84) Suppress the border on the last item

TIER 3 — Numeric / spacing token divergences (mechanical)

Step body reuses LEGACY tokens instead of the g-* ones the standalone block already uses correctly (step_block.dart): - step vertical padding 16 → 13.6 (gStepPadV); disc↔body gap 16/12-mobile → 13.6 (gStepGap, no mobile split) - instruction text size 14.4 → 15 (gStepTextFontSize); color #555 → #3a3a3a (gBody) - question prompt: needs its own margin:.1rem 0 .6rem (1.6/9.6) vs instruction (.g-q-prompt is a distinct class) - section-title letter-spacing 0.5 → 1.1 (.1em); margin-bottom 8 → 11.2 (.7rem)

Step interactions — double-counted top margins (host wraps each atom in Padding(top:8) AND the atom self-applies its own top margin → 16 vs web 8): Tip, Explanation, Gallery, Flashcard. Fix by removing the host-side padding (atoms own gCalloutMarginTop). - checkbox size ~18 → 16; label unchecked color #666 → #6b7280 (gMuted); checkbox margin-top 12 → 10.4; answer-group margin-top 8 → 12

Materials: - body→qty gap missing 12px (web .g-mat gap:.75rem + qty pad = 18.4 total; Flutter only 6.4) - section-title letter-spacing 0.5→1.1, margin-bottom 8→11.2 (shared with step) - group-header letter-spacing 0.5 → 0.66 (.06em) — it's wrongly sharing the section-title token; needs its own gGrouphdrLetterSpacing - doc-type thumb glyph 20 → 22 (.g-tile-ic); opt-badge Wrap adds an extra 4px on top of its 6px margin; gallery margin-top 6→6.4, summary pad-V 2→2.4

Metadata: - key font-weight w600 → w400 (web key is normal weight); key letter-spacing 0.3 → 0.44 (.04em); align baseline vs top (minor); link underline missing (minor, uncertain — see CNV)

Footer: progress fill snaps — web tweens width .4s ease (add a width tween); mobile bottom pad 10 → 8 (safe-area inset itself is handled). Shell: desktop close-button offset 8 → 12 (.75rem); mobile matches at 8.

TIER 4 — DEAD code (retire as the fixes land — bidirectional cleanup)

Legacy tokens with zero references after 22.4 (verified by the audits): - guideLightboxResetColor, guideLightboxResetHoverColor (footer reset now uses brand-ink) - guideLightboxStepNumberSize/…SizeMobile/…Weight/…FontSize/…FontSizeMobile/…Bg/…Color/…CompletedBg/…CompletedColor/…IncorrectBg/…IncorrectColor (disc now StepDisc) - guideLightboxStepTitleSize/…Weight/…Color, guideLightboxStepBorder, guideLightboxStepCompletedOpacity - guideLightboxStepMediaMargin, guideLightboxStepMediaRadius (step media now MaterialThumb+gStepMediaMarginTop) - guideLightboxCheckboxAccent (once #12 switches to colorScheme.primary) ⚠️ KEEP (still used by learn-mode overview/lesson/review — 22.5): guideLightboxStepBadge*, guideLightboxSectionTitle*, and other guideLightbox* referenced by learn files. Verify each with grep before deleting.

ACCEPTED-DIVERGENCE (not misses)

    • Reset modal chrome — Flutter uses the g-styled card (icon badge + red callout); web guide modal is plain Bootstrap. Operator-approved 2026-07-02; copy/labels/buttons all MATCH. Web-side gap flagged back to web team.
    • Close-button hover (scale+brand) — no touch analog; ignorable.

COULD NOT VERIFY (residual risk — the ~7%)

    1. Server redaction of is_correct in the hydration payload (services/guide_progress.go EnrichGuideEdges/BuildProgressJSON) — Tier-1 #6 depends on it. If is_correct is redacted for answered steps, Flutter shows NO correct-highlight on hydration (a different, worse divergence). Quick backend check recommended.
    2. Link underline browser default (metadata #) — no bare-a reset in kon_*.css, but a global/normalize sheet outside kon_*.css wasn't audited.
    3. Flashcard-only reset visibility — whether a cook-mode flashcard reveal alone counts as "has progress" for showing the Reset button.
    4. Runtime-pixel confirmation — these were static source reads; sub-pixel values (Material Checkbox glyph size, baseline alignment) and the disc/last-border fixes want a live smoke.
← Back to Under the Tuscan Sun