feat: DM Sans typeface, real example previews, and hub logo/Recent fixes#35
Open
stephschofield wants to merge 8 commits into
Open
feat: DM Sans typeface, real example previews, and hub logo/Recent fixes#35stephschofield wants to merge 8 commits into
stephschofield wants to merge 8 commits into
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three related hub/UI improvements, aligning the app with the Terminal 42 look & feel and fixing two hub usability gaps:
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.
@fontsource-variable/dm-sans, bundled viapackages/ui/src/fonts.ts.--font-sansand--font-displayat"DM Sans Variable"intokens.css(mono stack already matched T42).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.
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.ExampleCardPreview: lazy-mounted (IntersectionObserver), scaled to cover (ResizeObserver), sandboxed + motion-frozen iframe — same techniqueDesignCardPreviewuses 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.homei18n string (en/zh-CN/pt-BR) +topbar-button-hometest id.Hide empty designs: Recent showed empty shells (aborted generations, repro/test entries with no snapshot) indistinguishably from real work.
listDesigns()now attachessnapshotCountvia a correlatedCOUNTsubquery;Designschema gains an optionalsnapshotCountfield.RecentTabgains 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. newsnapshotCountDB coverage)Notes
snapshotCountfilter 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.snapshotCountis 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-designfork. With it merged, everything of value from the fork lives inAll-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:
Divergence audit (fork
main@ 2026-06-23 vs orgmain@ 2026-07-07)I compared the fork and this repo at the file-tree level before opening this PR:
packages/shared/src/css-vars.tshere handles@media/@supports/@layertoken nesting plus an input-size cap; the fork still had the older, simpler parser. Same forpackages/core/src/agent.ts,packages/runtime/src/index.ts.verify-loop-spike.test.tsand a Dependabot version-skew guard — neither a missing feature.A literal
fork-main → org-mainmerge 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 andanalysis/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
stephschofield/atv-designcan be archived/deleted — no unique work remains in it.Review hardening (santa-loop)
Adversarial dual-review (Claude Opus + Codex) surfaced three fixes, all applied with regression tests:
hub.recent.allEmptycopy was previously dead (DesignGrid suppresses its empty label whenever the+ New designprefix tile is present). RecentTab now renders an explicit hint when the hide-empty filter collapses the whole list.aria-hiddenwrapper,tabIndex={-1}) so they no longer inject keyboard-focusable frames into the Examples tab order.selectRecent/countEmpty/shouldShowAllHiddenHint,injectThumbnailStylebranches, and the logo→home navigation intent.getDesignnow computes the realsnapshotCountvia the same correlated subquerylistDesignsuses, soduplicateDesign/renameDesign/updateWorkspacereturn paths no longer misreport non-empty designs as empty shells (with DB regression tests).shouldOfferHideEmpty) so it never appears as a no-op control when the only empty designs sit outside the 6-card window.