Skip to content

TSP: theme-token styling, print-and-cut stationery, setup wizard on shared Stepper (CS-12101)#663

Merged
lucaslyl merged 14 commits into
mainfrom
cs-12101-tsp-ui-tokens
Jul 17, 2026
Merged

TSP: theme-token styling, print-and-cut stationery, setup wizard on shared Stepper (CS-12101)#663
lucaslyl merged 14 commits into
mainfrom
cs-12101-tsp-ui-tokens

Conversation

@lucaslyl

@lucaslyl lucaslyl commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

What

Converts the Table Seating Planner to the pure theming system, adopts boxel-ui components across the planner, and layers a full print-and-cut stationery pipeline on top.

Theming adoption (CS-12101)

Theme cards instead of BrandGuide

  • Theme/italian-cathedral.json and Theme/double-happiness-banquet.json rewritten in place as lean plain Theme instances (cssVariables with :root{} + .dark{}, cssImports for fonts); new Theme/parisian.json default
  • The bespoke buildThemeVars() regex pipeline (inlining a BrandGuide's :root onto the card root) is deleted — themes now flow through the standard CardContainer scoped-style pipeline, which also makes dark mode work for free via the container-query .dark block

Three-scope token resolution everywhere

  • Every visual value in every planner module (tsp.gts, guest.gts, host.gts, table.gts, fixture.gts, person.gts, layout-template.gts, card-views.gts, seating-plan-popover.gts, setup-wizard.gts, fixture-glyph.gts, layout-preview.gts) resolves through var(--tsp-X, var(--semantic, literal)) — component knob → semantic theme token (shadcn vocabulary) → brand literal
  • The 25 bespoke Parisian names (--paper, --ink, --navy, --gold, …) and ~445 hardcoded hexes are gone; fills and text always use paired tokens (--primary/--primary-foreground, --accent/--accent-foreground) so contrast holds under any theme
  • @import font lines removed from component CSS — fonts come from the theme's cssImports, with literal font names kept as var-chain fallbacks

Themeless ≠ ugly

  • When no theme is linked, a conditional tsp-default-theme class pins the full Parisian semantic set on the card root, so app-level defaults can't restyle the planner arbitrarily; linked themes omit the class and win

Theme across portals

  • The shared Popover (46f065-popover) bridges the theme into its portal: primary = adopt the anchor's data-boxel-theme-scope attribute; fallback = copy resolved semantic pairs for element-pinned palettes
  • Same trick for printing: the hoisted print sheet re-adopts the theme scope (or the default-palette class) so stationery keeps its colors outside the planner subtree

boxel-ui adoption

  • Hand-rolled toolbar/inspector/popover controls replaced with <Button> (@kind mapped: primary CTA → primary, outlined → secondary, ghost → text-only) while keeping the existing scoped classes as re-skins — focus states, disabled semantics, and theme-token defaults come from boxel-ui underneath
  • Text inputs replaced with <BoxelInput> (header event title/date/venue, table name, seats stepper, wizard fields); the seats stepper pins BoxelInput's default width:100% back to a compact −/value/+ group
  • Button being inline-flex swallowed the literal icon–label space; restored with gap on labeled buttons (including the portaled popover's Duplicate/Delete)
  • Non-standard shapes (toggle chips, canvas seat dots, circular glyph closes) intentionally stay hand-rolled

Setup wizard

  • Rebuilt on the shared aef6db-stepper Stepper component; all steps mandatory, ✕ close as the escape hatch; host/guest fitted cards render in the wizard panels via field components

Host / Guest

  • Rich 4-family fitted templates (badge / strip / tile / card via @container fitted-card queries) plus editorial isolated views; Host.role is an enumField (Guest.category precedent); Guest's custom edit template removed so both use the default editor

Event date

  • eventDate is a DatetimeField; header + wizard use datetime-local inputs and invite copy / fitted labels include the time; old date-only instance data still deserializes

Print & cut (A4)

  • Print sheet hoisted to <body> on beforeprint/afterprint with a global print stylesheet — any print path (menu, Ctrl+P, headless page.pdf()) emits only the cards
  • Explicit A4 pagination (printPages): rows packed per page, 12mm margins on every page, cards never straddle a page boundary
  • Illustrator-style bleed: each cell is trim + 2mm bleed with corner trim marks outside the bleed; no outline or decorative frame at the cut line
  • @page { size: A4; margin: 0 } suppresses the browser's date/URL header/footer; print-color-adjust: exact prints backgrounds regardless of the background-graphics option
  • Stationery: theme-primary table names with flanking hairline rules, character-count fit-to-width names, optional event-logo watermark at 8%, print-deepened small-caps kickers

Testing

  • pnpm run lint (eslint + template-lint + glint) green throughout
  • Live-verified in host: themeless + italian-cathedral + double-happiness instances (headers, rails, popovers, fitted views, dark mode), seat popover controls, and print PDFs generated headlessly via Puppeteer page.pdf({ format: 'A4' }) — multi-page, themed, trim-marked, no host chrome

Linear: CS-12101

lucaslyl added 14 commits July 17, 2026 11:32
…n default

italian-cathedral and double-happiness-banquet keep their file paths (so
instance links are untouched) but slim from BrandGuide documents to plain
Theme cards: semantic tokens in :root, dark-mode variables in .dark, font
cssImports, plus a small --tsp-* extras block (motif, grid, accent/primary
shades). parisian.json is new — the components' built-in fallback palette
promoted to a linkable Theme so the classic look is an explicit choice.
…cope

The portaled root now takes the anchor's data-boxel-theme-scope
attribute, so the theme's scoped stylesheet styles it directly — the
full token vocabulary (semantic pairs, custom namespaces, future
additions) flows with nothing to enumerate, and dark-mode container
queries keep working. Anchors with no theme scope fall back to copying
the resolved semantic tokens (full fill/foreground pairs) from the
anchor, which is what carries an element-pinned default palette across
the portal; the previous list missed the companion foregrounds and
produced unreadable text on themed fills.
A soft accent wash now sits behind the active rail row, so 'you are
here' stays readable even once the step completes and its dot flips to
the done fill; the active dot's halo is stronger, and a completed step
that is also current keeps the on-accent foreground for its check
instead of being repainted in ink.
Styling now flows through the semantic theme tokens with a three-scope
chain on every value — var(--tsp-X, var(--semantic, literal)). The 25
bespoke Parisian names (--paper/--navy/--gold/...) are retargeted onto
--background/--primary/--accent and friends; the bespoke buildThemeVars
inline pipeline is gone in favor of the standard CardContainer scoped
style (dark mode works); font @imports move to the themes' cssImports;
SVG decor derives from --accent via currentColor; a tsp-default-theme
class pins the Parisian palette when no theme is linked so app-level
tokens can't restyle the card.

Standard-shaped controls adopt boxel-ui: toolbar and rail actions and
the save-template form use <Button>/<BoxelInput>, re-skinned by the
existing classes and anchored to the --primary/--primary-foreground
pair.

The planner's fitted template is rebuilt as four container-query format
families (badge/strip/tile/card) with the real floor plan as the
tile/card hero and content panels painted with the theme's primary.

UX: the inspector handle gains a vertical Details label; host avatars
in the header are display-only with a hover top-right unlink button.
Host gains an editorial isolated view (masthead, gold portrait ring,
editable name, role as enum pills reusing the guest chip pattern) and
role becomes an enumField over the six wedding roles. Both cards get
four-family fitted templates (badge/strip/tile/card via the fitted-card
container) in the invitation language — Host on the primary spine,
Guest on cream with category dot and VIP star — with names painted in
the theme's primary. Contrast pairs are enforced throughout (content on
accent fills uses --accent-foreground; accent is never light-background
text), and the isolated/fitted roots pin the default palette when no
theme is linked. Also fixes two invalid color: var(#hex) declarations.
…mandatory

The wizard's own shell (step state machine, rail, dots, action bar,
modal scrim — ~250 lines) is replaced by the catalog <Stepper>; this
file keeps only the four domain steps, the gold corner ornament, and
three Stepper knobs (serif heading font, gold rail accents with a cream
check, primary-tinted scrim). The --wz-* palette layer is gone — colors
flow from the semantic tokens.

The flow is now mandatory: step 1 needs name, date, AND venue; hosts
and guests each need at least one entry; there is no Skip. The ✕ close
remains as the escape hatch — dismissal isn't persisted, so the wizard
reopens for incomplete planners. Newly added hosts and guests render as
fitted cards in a responsive grid inside the step panels, and the event
placeholders use generic examples.
The setup wizard now builds on the catalog Stepper, so the listing's
spec set includes that cross-listing dependency alongside the planner's
own 26 module specs.
The header and setup wizard now use datetime-local inputs so guests
know when, not just which day; old date-only instance data still
deserializes.
Host never had a custom edit, so Guest matching it makes editing
consistent (default field editors; category stays an enumField
dropdown). The strip fitted view gains a hairline inset frame, a soft
gradient wash, and a muted ❧ ornament where the VIP tag would sit.
- table name in the theme primary with flanking hairline rules; the
  bottom ✦ ornament echoes it
- fit-to-width names: font size scales by character count (the print
  sheet is hidden on screen, so it can't be measured)
- optional event-logo watermark at 8% behind the card content
- print deepens the small-caps kicker (accent mixed 50% into ink) for
  paper contrast, and print-color-adjust keeps backgrounds without the
  browser's background-graphics option
Printing (A4 print & cut):
- print sheet hoisted to <body> on beforeprint/afterprint with a global
  print stylesheet, so ANY print path (menu, Ctrl+P, headless page.pdf)
  emits only the cards — no operator-mode chrome, no clipped pagination
- the hoisted sheet re-attaches its theme (adopts the linked theme's
  scope attribute, or the pinned default palette class) so stationery
  keeps its colors outside the planner subtree
- explicit A4 pagination (printPages): rows are packed per page so
  every page keeps a 12mm margin on all four sides and a card can never
  straddle a page boundary
- each cell is its own bleed box (trim + 2mm bleed) with
  Illustrator-style trim marks outside the bleed; no outline or
  decorative frame at the cut line
- @page { size: A4; margin: 0 } so the browser has nowhere to draw its
  date/URL header and footer

UX:
- inspector panel and its Details handle only render when a table,
  fixture, or seat element is selected; the stale Compose placeholder
  is gone
- seats stepper in the table popover pinned to a compact −/value/+
  group (BoxelInput defaults to width:100%)
- icon-label gap restored on Duplicate/Delete and other flex buttons
- header date is a datetime-local input; invite copy and fitted labels
  include the time
- consecutive @Tracked declarations tightened to single lines
@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown

Staging Submissions Preview

This PR's content is pushed to the staging submissions realm: https://realms-staging.stack.cards/submissions/

Changed listings:

Changed folders:

  • 41e20f-wedding-table-seating-planner/
  • 46f065-popover/
  • aef6db-stepper/

Updated at 2026-07-17 07:51:25 UTC for commit c90d4ac. Shared realm: content reflects whichever PR pushed last.

@lucaslyl
lucaslyl requested a review from a team July 17, 2026 07:52
@lucaslyl lucaslyl self-assigned this Jul 17, 2026
@lucaslyl
lucaslyl merged commit 7c83f54 into main Jul 17, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant