Skip to content

feat: DM Sans typeface, real example previews, and hub logo/Recent fixes#35

Open
stephschofield wants to merge 8 commits into
mainfrom
worktree-t42-dm-sans-fonts
Open

feat: DM Sans typeface, real example previews, and hub logo/Recent fixes#35
stephschofield wants to merge 8 commits into
mainfrom
worktree-t42-dm-sans-fonts

Conversation

@stephschofield

@stephschofield stephschofield commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Summary

Three related hub/UI improvements, aligning the app with the Terminal 42 look & feel and fixing two hub usability gaps:

  1. DM Sans typeface — adopt Terminal 42's primary font across the app chrome.
  2. Real canvas previews on Example cards — replace abstract SVG placeholders with believable rendered designs.
  3. Hub fixes — make the logo clickable (returns home) and add a "Hide empty designs" filter to Recent.

1. Adopt DM Sans to match Terminal 42 (4fb2fd2)

Terminal 42 uses DM Sans (OFL, MIT-compatible) as its primary typeface with JetBrains Mono for monospace. The app previously used Geist/Fraunces.

  • Add @fontsource-variable/dm-sans, bundled via packages/ui/src/fonts.ts.
  • Point --font-sans and --font-display at "DM Sans Variable" in tokens.css (mono stack already matched T42).
  • Dark-mode palette already aligned (deep near-black canvas, sky-blue accent).

2. Real canvas previews on example cards (a7c9716)

The Examples gallery rendered abstract SVG placeholders, so examples read as decorative stand-ins rather than what each prompt produces.

  • Add packages/templates/src/examples/previews.ts: 21 hand-authored, fully self-contained HTML canvases (one per example), each designed for a 1280×800 card, kept under ~4 KB, using the app's DM Sans feel.
  • Add ExampleCardPreview: lazy-mounted (IntersectionObserver), scaled to cover (ResizeObserver), sandboxed + motion-frozen iframe — same technique DesignCardPreview uses for user designs. Falls back to the SVG thumbnail when no preview exists.
  • previews.test.ts: every example has a valid, self-contained, script/network-free preview; no orphan keys.

3. Clickable logo + hide-empty Recent filter (ad17427)

Logo → home: the ATV Design wordmark was decorative. Wrap it in a button that returns to the hub home (Recent tab). Adds topbar.home i18n string (en/zh-CN/pt-BR) + topbar-button-home test id.

Hide empty designs: Recent showed empty shells (aborted generations, repro/test entries with no snapshot) indistinguishably from real work.

  • listDesigns() now attaches snapshotCount via a correlated COUNT subquery; Design schema gains an optional snapshotCount field.
  • RecentTab gains a persisted "Hide empty designs" toggle (shown only when empty designs exist), filtering to designs with a canvas.

Test plan

  • @atv-design/ui — 19 tests
  • @atv-design/templates — 12 tests (incl. new previews coverage)
  • @atv-design/shared — 232 tests
  • @atv-design/i18n — 13 tests (locale key parity)
  • @atv-design/desktop — 1221 tests (incl. new snapshotCount DB coverage)
  • Typecheck clean across all touched packages; Biome clean.
  • All 21 previews render as believable product screenshots at card scale (verified headlessly).
  • Logo→home and hide-empty filter verified end-to-end by driving the live app over CDP (3 seeded designs → filter hides 2 empty shells; logo returns to Recent from Examples).

Notes

  • The snapshotCount filter relies on the main-process DB change, so it takes full effect on a fresh app launch (normal user path) rather than renderer-only HMR.
  • snapshotCount is optional in the schema for back-compat with existing Design fixtures/persisted payloads.

📦 Fork consolidation — retiring the private fork

This PR is the final piece of work from the private stephschofield/atv-design fork. With it merged, everything of value from the fork lives in All-The-Vibes/ATV-Design, and the private fork can be retired.

How we got here

The fork's major work was already upstreamed into this repo through earlier consolidated PRs:

Fork work Landed upstream via
Terminal 42 × ATV integration (translation layer, DesignCanvas split, dark reskin, attribution) #25
Azure OpenAI / Foundry provider via Entra ID #17
Phase 0.5 spikes (static-tweak bridge, Azure throttle reason, smoke-green) folded into #25
Dependabot dep bumps (react, electron, vitest, playwright, tailwind, typescript) Dependabot PRs on this repo

Divergence audit (fork main @ 2026-06-23 vs org main @ 2026-07-07)

I compared the fork and this repo at the file-tree level before opening this PR:

  • Org is newer or equal on every shared source file. The org repo superseded the fork after absorbing its work — e.g. packages/shared/src/css-vars.ts here handles @media/@supports/@layer token nesting plus an input-size cap; the fork still had the older, simpler parser. Same for packages/core/src/agent.ts, packages/runtime/src/index.ts.
  • Zero fork-only non-test source files. No user-facing feature is stranded in the fork.
  • The only fork-only files were two tests: a throwaway Phase-0.5 verify-loop-spike.test.ts and a Dependabot version-skew guard — neither a missing feature.

A literal fork-main → org-main merge was therefore not the right move: it would have been ~90 files / +4375 −1806 that reverted newer upstream source and dumped ~30 MB of fork-internal demo videos and analysis/ planning docs into the org repo.

The genuinely-new, not-yet-upstream work was authored this session directly against org main — the three commits in this PR. Merging this completes the consolidation.

After merge

  • The private fork stephschofield/atv-design can be archived/deleted — no unique work remains in it.
  • No further fork-maintenance (rebasing, dep-bump reconciliation) needed.

Review hardening (santa-loop)

Adversarial dual-review (Claude Opus + Codex) surfaced three fixes, all applied with regression tests:

  • All-empty Recent state now renders. The hub.recent.allEmpty copy was previously dead (DesignGrid suppresses its empty label whenever the + New design prefix tile is present). RecentTab now renders an explicit hint when the hide-empty filter collapses the whole list.
  • Example preview iframes are decorative (aria-hidden wrapper, tabIndex={-1}) so they no longer inject keyboard-focusable frames into the Examples tab order.
  • Renderer logic is now unit-tested (16 tests): selectRecent/countEmpty/shouldShowAllHiddenHint, injectThumbnailStyle branches, and the logo→home navigation intent.
  • getDesign now computes the real snapshotCount via the same correlated subquery listDesigns uses, so duplicateDesign/renameDesign/updateWorkspace return paths no longer misreport non-empty designs as empty shells (with DB regression tests).
  • The hide-empty switch is gated on the visible Recent set (shouldOfferHideEmpty) so it never appears as a no-op control when the only empty designs sit outside the 6-card window.

Terminal 42 uses DM Sans as its primary typeface (globals.css /
tailwind.config.js) with JetBrains Mono for monospace. Align the ATV
Design app chrome to the same feel:

- Add @fontsource-variable/dm-sans (OFL, MIT-compatible)
- Bundle DM Sans via packages/ui/src/fonts.ts alongside existing faces
- Point --font-sans and --font-display at 'DM Sans Variable' in tokens.css
  (JetBrains Mono mono stack already matches T42)

Dark-mode palette already aligns with T42 (deep near-black canvas,
sky-blue accent). Verified DM Sans loads and paints via headless render;
ui + desktop suites green (19 + 1195 tests).
The Examples gallery rendered abstract SVG placeholders for every card,
so examples read as decorative stand-ins rather than what each prompt
produces. Replace them with real, self-contained HTML canvas previews
rendered in a scaled, sandboxed, motion-frozen iframe — the same
technique DesignCardPreview uses for user designs.

- Add packages/templates/src/examples/previews.ts: 21 hand-authored,
  fully self-contained HTML canvases (one per example), each designed
  for a 1280x800 card and kept under ~4 KB, using the app's DM Sans feel.
- Export EXAMPLE_PREVIEWS / getExamplePreview from templates.
- Add ExampleCardPreview: lazy-mounted (IntersectionObserver), scaled to
  cover (ResizeObserver), sandboxed iframe; falls back to the existing
  SVG thumbnail when no preview exists.
- Wire ExampleCard to render the preview under its title scrim.
- Add previews.test.ts: every example has a valid, self-contained,
  script/network-free preview; no orphan keys.

Verified all 21 render as believable product screenshots at card scale
and stay legible under the title scrim. templates + desktop suites green
(12 + 1195 tests).
Two hub usability fixes:

1. The ATV Design wordmark in the top bar was decorative — clicking it
   did nothing. Wrap it in a button that returns to the hub home (Recent
   tab). Adds topbar.home i18n string (en/zh-CN/pt-BR) and a
   topbar-button-home test id.

2. Recent showed empty design shells (aborted generations, repro/test
   entries with no snapshot) indistinguishably from real work. Surface a
   snapshot count so the hub can tell them apart:
   - listDesigns() now attaches snapshotCount via a correlated COUNT
     subquery; Design schema gains an optional snapshotCount field.
   - RecentTab gains a persisted 'Hide empty designs' toggle (shown only
     when empty designs exist), filtering to designs with a canvas.

Tests: snapshotCount coverage in snapshots-db.test.ts; shared/i18n/
desktop suites green (232 + 13 + 1196).
… renderer tests

Address santa-loop review findings (round 1):
- Codex (correctness): RecentTab passed t('hub.recent.allEmpty') as DesignGrid's
  emptyLabel, but DesignGrid only shows emptyLabel when prefixTile is absent —
  and RecentTab always passes the '+ New design' tile, so the all-hidden copy
  was dead. Now RecentTab renders an explicit all-empty hint via a pure
  shouldShowAllHiddenHint() helper when hideEmpty collapses the whole list.
- Codex (a11y regression): example preview iframes were keyboard-focusable,
  changing tab order vs the decorative SVG thumbnails they replaced. Mark the
  preview wrapper aria-hidden and the iframe tabIndex={-1}.
- Both reviewers (coverage): new renderer logic shipped without tests. Add pure
  unit tests for selectRecent/countEmpty/shouldShowAllHiddenHint, the
  injectThumbnailStyle head/no-head/case branches, and the goHome navigation
  intent + home test-id contract (16 new tests, apps/desktop no-mount convention).
…n misreport)

Address santa-loop review findings (round 2):
- Codex (correctness/consistency): snapshotCount was only populated by
  listDesigns(); getDesign() used SELECT * and rowToDesign() coerced the missing
  column to 0. So every single-design return path (getDesign, renameDesign,
  updateWorkspace, and especially duplicateDesign — which clones snapshots then
  returns getDesign(newId)) misreported non-empty designs as empty shells.
- getDesign() now attaches snapshot_count via the same correlated COUNT subquery
  listDesigns() uses, so all return paths agree.
- Add regression tests: getDesign reports the real count; duplicateDesign
  returns the clone with snapshotCount === 1 (and listDesigns agrees).
…rol)

Address santa-loop review findings (round 3):
- Codex (correctness): the 'Hide empty designs' switch was shown whenever ANY
  live empty design existed (countEmpty), but Recent only renders the first 6.
  If every empty design sat outside that window, the switch appeared yet
  toggling it changed nothing — a no-op control. Now gated on
  shouldOfferHideEmpty(), which compares the filtered vs unfiltered visible set
  and only offers the switch when it actually changes what's shown.
- shouldShowAllHiddenHint now also reasons about the visible window (takes the
  limit) for consistency.
- Add regression tests: switch hidden when empties are outside the window;
  shown when an empty design is within it.
… real card)

Address santa-loop review findings (round 4):
- Opus (correctness/consistency): shouldShowAllHiddenHint keyed off the
  UNFILTERED top-limit window (shown.every(isEmpty)), but the grid renders the
  FILTERED set. When the newest N designs are all empty and a real design sits
  just outside that window, hideEmpty surfaces the real design into the grid
  while the hint still fired — grid showed a canvas AND 'no designs with a
  canvas yet' at once. Now keyed off the filtered visible set being empty while
  live designs exist, matching what the grid actually renders.
- Add regression test: newest-3 empty + 1 real outside window → hint is false.
…createdAt DESC)

Address santa-loop review findings (round 5):
- Codex: selectRecent sorted by updatedAt only and never returned 0, dropping
  the createdAt tiebreaker that listDesigns (updated_at DESC, created_at DESC)
  documents. Two designs with equal updatedAt but different createdAt could be
  ordered opposite to the DB contract, and the never-0 comparator was unstable
  for ties. Now mirrors the DB order and returns 0 on a true tie (stable).
- Add regression tests: createdAt tiebreak ordering, and stable order on a full tie.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant