Skip to content

feat(skills): design picker UI (Phase 1 templates index + Phase 2 fine-tune)#974

Closed
vanceingalls wants to merge 1 commit into
feat/picker-build-scriptfrom
feat/picker-chrome
Closed

feat(skills): design picker UI (Phase 1 templates index + Phase 2 fine-tune)#974
vanceingalls wants to merge 1 commit into
feat/picker-build-scriptfrom
feat/picker-chrome

Conversation

@vanceingalls
Copy link
Copy Markdown
Collaborator

@vanceingalls vanceingalls commented May 19, 2026

Summary

The picker UI itself — a single ~6k-line HTML file with two phases. Built by build-design-picker.py (PR #973), opened by hyperframes pick (PR #975).

Scope

  • 1 file: skills/hyperframes/templates/design-picker.html
  • ~12k lines (HTML + scoped CSS + vanilla JS, no framework)
  • Reviewable as one cohesive surface

Phases

Phase 1 — Templates index

Editorial-catalog layout. Source Serif 4 italic + Hanken Grotesk on warm-tinted OKLCH neutrals. Roman-numeral folio markers, hairline rules, generous space.

  • Sticky palette bar at the top — each palette is a row of i. Name · 4-cell swatch strip, horizontally scrollable, ~60px tall (much shorter than a chip card).
  • 3-up specimen grid — each card renders the template's design.html cover (from PR feat(skills): hand-crafted design.html for each template #972) via client-side token substitution + blob URL. No card borders, no shadows, hairline rule under the preview, italic tagline below.
  • Per-card iframe scaling — each card is 16:9; the iframe inside is 1920×1080, scaled down via transform: scale(containerWidth/1920).
  • Roman-numeral folios (i. — xxxiv.) and 01 / 34 counters in the gutter.

Phase 2 — Fine-tune

Sidebar with the picker controls + a right pane showing the chosen template's design.html in an iframe. Picker controls inject --primary / --secondary / --tertiary / --accent / --tp-* tokens directly on the iframe's documentElement on every change.

Sections:

  • Palette — 5-6 curated palettes per prompt + Template Default + custom palette editor
  • Typography — 5-6 type pairings, font discovery via the picker's JSON manifest
  • Corners / Density / Depth — chips. These are scoped to .demo-card only (no leakage to other surfaces).
  • Motion — easing curves with live preview, custom-bezier editor included
  • Background — three.js shader presets (vertex + fragment GLSL), brightness/contrast/saturation/grain config persists across preset changes via window._sgRender
  • Use-palette toggle — shader can either use the picked palette colors or three custom color pickers

Per-swatch text color

Each palette swatch's text color is computed from the bg luminance so previews stay legible regardless of palette. Inserted at picker-render time by build-design-picker.py (PR #973).

Phase 1 → Phase 2

Palette pick from Phase 1 is carried forward into Phase 2 (picks.palette = tpActivePal). Sidebar scrolls to top + iframe preview scrolls to top on every template change.

Hot spots for review

  • Token injection paths. tpApplyAllPalettes injects both --tp-* and the --primary/--secondary/--tertiary/--accent design.html tokens on the iframe document. --tp-accent is intentionally stripped from the scoped template CSS so cascade inheritance flows from html style attribute → .ds-slide-frame without circular var() references.
  • Shader uniform rebake. _lastBgModeKey is reset after every renderDesignIframe call so palette changes force the shader uniforms to re-bake from the current palette via getBgColors().
  • tpScaleIframe selector. Matches both .idx-spec-preview (new editorial layout) and legacy .cprev so existing callers still work.
  • _tpNameSplit — splits template names on " (", then space, then CamelCase. Handles BlockFrameBlock / Frame, People's Platform (Block & Bold)People's Platform / (Block & Bold).

What's not in scope

Test plan

  • hyperframes pick (from PR feat(cli): hyperframes pick — open the design picker, gated behind feature flag #975) opens the picker
  • Phase 1 renders 34 specimens with their authored colors
  • Picking a palette in Phase 1 re-themes every card
  • Picking a template advances to Phase 2 with the palette carried forward
  • In Phase 2: changing palette updates the iframe + shader bg
  • In Phase 2: changing corners / density / depth updates only the .demo-card in the Surface section
  • In Phase 2: changing motion re-runs hero entrance animation + scrolls iframe to top
  • In Phase 2: changing shader preset preserves brightness / contrast / saturation / grain config

Stack

Depends on #971, #972, #973. Followed by #975 (CLI).

🤖 Generated with Claude Code

Copy link
Copy Markdown
Collaborator Author

vanceingalls commented May 19, 2026

@vanceingalls vanceingalls changed the base branch from feat/picker-build-script to graphite-base/974 May 19, 2026 22:02
Copy link
Copy Markdown
Collaborator

@jrusso1020 jrusso1020 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read the structural surface of design-picker.html (7192 lines, single file). Per the rubric this is one cohesive surface — focused review on phase boundaries, the token-injection scheme, and cross-PR consistency rather than a CSS/JS line-by-line walk. Posting as COMMENT (not stamping) per merge policy.

Phase boundary verification

Lines 1554-1556 declare the phase tabs (tab-mood → Phase 1 "Template", tab-tune → Phase 2 "Fine-tune"). Lines 1566-1582 declare the corresponding phase containers (#phase-mood is display:active initially, #phase-tune hidden via the .phase not-.active rule at line 104-110). The Phase 1 / Phase 2 split described in the PR body matches the implementation boundaries — no Phase 2 surface leaking into Phase 1's render path, no unfinished hooks I could spot. ✓

The "Phase 1 → Phase 2" handoff documented in the PR body (picks.palette = tpActivePal + scroll-to-top on template change) is the right shape for state passthrough.

Cross-PR token-substitution consistency

Verified the __X_JSON__ / __BASE_HREF__ placeholders in design-picker.html against build-design-picker.py's substitutions in #973:

Picker-side placeholders consumed by the build script (matched 1:1):

  • __BASE_HREF__
  • __PALETTES_JSON__
  • __TEMPLATES_JSON__
  • __ARCHITECTURES_JSON__
  • __MOODBOARDS_JSON__
  • __PROMPT_JSON__
  • __PROMPT_TEXT_JSON__
  • __TYPEPAIRS_JSON__

One mismatch flagged on #973's review: __PROMPT_DESC__ is substituted by the build script but does NOT appear in design-picker.html. Silent no-op currently. Either drop from the schema or add the placeholder where the prompt description should land. Tracked on #973; mentioning here for cross-stack visibility.

Other __*__ placeholders in the picker HTML (__PRIMARY__, __ACCENT__, __BG_TYPE__, __EASING_DESC__, __SHADER_VERTEX__, etc.) are not build-script substitutions — they're literal sentinels that travel into the design.html iframe contents and get substituted client-side at runtime per the PR body's "client-side token substitution + blob URL" claim. Cross-stack consistency holds as long as the matching design.html files in #972 don't carry orphan sentinels at render time. PR #972's test plan calls out a manual visual check for this — please make sure that happens.

Hot-spots review

  • tpApplyAllPalettes token injection — the documented behavior (injecting both --tp-* and --primary/--secondary/--tertiary/--accent on iframe documentElement, while stripping --tp-accent from scoped template CSS) is the right shape to avoid circular var() references. Subtle correctness path; worth verifying once with DevTools (compute the resolved --accent value on a .demo-card after a palette change — should resolve via the iframe documentElement style attribute, not from a circular cascade).
  • Shader uniform rebake via _lastBgModeKey reset — described in PR body. The rebake-on-palette-change is the right shape; the risk is "shader uses palette" toggle ↔ "shader uses custom three colors" toggle clobbering the rebake key. Worth a manual check.
  • tpScaleIframe selector matches both .idx-spec-preview and .cprev — documented as legacy-compat. Worth a follow-up to grep the codebase for .cprev usage and drop the alias once nothing references it.
  • _tpNameSplit name-splitting heuristic — handles BlockFrameBlock / Frame, People's Platform (Block & Bold) etc. Edge cases worth pinning by test if any are ever surfaced as bugs.

Non-blocking

  • 7192 lines in one file is the cost of the framework-less approach (intentional per the PR body). Worth documenting why somewhere in the file header — future maintainers will reach for "split into modules" as the obvious cleanup, and the answer is probably "framework-less is intentional, keep it one file." A 3-line top-of-file comment captures that.
  • External CDN dependency on cdn.jsdelivr.net for GSAP + Three.js (lines 8-17). For an offline-capable picker this is fine since the picker is local-only via 127.0.0.1; would only matter if running on a network-isolated host. Probably not worth changing for an "open the picker, point at fonts.googleapis.com anyway" surface.
  • No new tests for the picker JS logic. The Phase 1 ↔ Phase 2 handoff (and the shader-preset-preserves-config behavior across palette changes) are the most-likely regression vectors. Hard to unit-test 7k of vanilla JS in a single file; a Playwright smoke test (open the picker, pick a palette, verify --accent resolves on the iframe doc) would lock in the highest-value invariants. Not a blocker.

Scope question

The PR title says "Phase 1 templates index + Phase 2 fine-tune" — both phases land here. That's correct given the picker is one HTML file, but worth confirming whether Vance considered splitting Phase 1 / Phase 2 into separate PRs for incremental review (probably not feasible given the shared JS state — agreed with the current shape).

No blockers from my side on this layer. The picker is a substantial chunk of work and the cross-PR consistency holds up.

— Rames Jusso

@vanceingalls vanceingalls changed the base branch from graphite-base/974 to feat/picker-build-script May 19, 2026 22:46
@vanceingalls vanceingalls force-pushed the feat/picker-build-script branch from b909b29 to 44c8912 Compare May 19, 2026 23:01
…e-tune)

design-picker.html is the picker shell — a single HTML file with two
phases.

Phase 1 (templates index): editorial-catalog layout (Source Serif 4
italic + Hanken Grotesk on warm-tinted OKLCH neutrals). Sticky compact
palette bar at the top. 3-up specimen grid where each card renders the
template's design.html cover via client-side token substitution + blob
URL. Roman-numeral folio markers, hairline rules.

Phase 2 (fine-tune): sidebar with palette / typography / corners /
density / depth / motion / background shader sections. Right pane shows
the chosen template's design.html in an iframe with --primary /
--secondary / --tertiary / --accent / --tp-* tokens injected on every
control change. Shader background uses three.js (postprocessing
EffectComposer with optional HalftonePass). Surface example card reacts
only to corners/density/depth picks — does not leak to other surfaces.

Per-swatch text color computed from luminance so palette previews stay
legible. Motion change re-runs hero entrance animation and scrolls the
iframe preview to the top. Phase 1 palette pick carried forward into
Phase 2.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants