Repository navigation
Widget visual styling: ADR-0006 + icon {source, name} schema - #36
Merged
Merged
Conversation
Records the design decisions from the #21 grilling: presentation (color, icon) relays opaquely through the daemon (amending ADR-0003), color is raw-hex background-only, icon becomes a {source, name} dispatch resolved against a client-owned renderer registry, the client bundles Lucide + Simple Icons, one fixed style (no light/dark), per-kind look is hard-coded client CSS with color button-only, and image/font-set uploads are deferred. Extensible custom control kinds split off to #35. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wx5giKP22zuCBmQE2YcPPm
The widget `icon` field changes from a bare string to a {source, name}
object: `source` selects a client-side icon renderer, `name` is the glyph
within it. The daemon validates both are non-empty strings and relays them
opaquely — it does not enumerate valid sources (client owns that registry).
- daemon: new `Icon` pydantic model; `Widget.icon: Icon | None`
- client: `Icon` type in protocol.ts; `Widget.icon` updated
- client: ButtonGrid renders `icon.name` as text for now — the dispatch to
real Lucide / Simple Icons glyphs lands with the renderer work in #21
- JogHandle drops its unused `icon` field (jogstrips ignore icons)
- all shipped layouts migrated to the {source: lucide, name: ...} form
- README documents the new icon shape
Structure-only step: no icon libraries bundled yet. Icon names are carried
over verbatim and will be reconciled against Lucide's actual vocabulary
when the renderer is added. 151 daemon tests pass; client typechecks.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wx5giKP22zuCBmQE2YcPPm
Wires the {source, name} icon dispatch to actual glyphs via a new
client/src/Icon.tsx renderer:
- source: lucide -> Lucide component, resolved kebab-case -> PascalCase.
Bundled whole (~198 KB gz) into the main chunk; renders synchronously.
- source: simple-icons -> brand path from the Simple Icons set, imported
lazily as a single on-demand chunk (~2.1 MB gz) the first time a layout
references a logo. Never fetched unless a brand icon is actually used.
- unknown source / unresolved name -> visible dashed placeholder.
Also:
- ButtonGrid renders the glyph above the label (icon-first reads better).
- Jogstrip "||" placeholder -> Lucide chevrons-up-down (the Q4 mark).
- style.css sizes the svg glyphs; drops the ::before "||" pseudo-element.
- Reconcile layout icon names to Lucide's real vocabulary: close->x,
columns->columns-2, media-play->play, paste->clipboard-paste,
refresh->refresh-cw, rows->rows-2, window->app-window. All 17 names now
resolve to real components (verified programmatically).
- ADR-0006 corrected: the "~0.5 MB bundle both whole" estimate was wrong;
records the Lucide-whole / Simple-Icons-lazy split and real sizes.
Client typechecks and builds; 151 daemon tests pass. Per ADR-0003, React
visuals are not covered by the test suite.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wx5giKP22zuCBmQE2YcPPm
Demo mode: a ``?demo=<name>`` URL param (firefox / default / showcase) renders a fixture ServerLayout with the WebSocket disabled, so the client can be viewed, screenshotted, or design-iterated without a running daemon. The showcase fixture exercises both renderers and the edge cases in one view (Lucide glyphs, per-button colour, lazily-loaded Simple Icons brand logos, a no-icon button, and an intentionally-unknown icon placeholder). Build: split the icon sets into named chunks — ``lucide`` (eager, ~148 KB gz) and ``simple-icons`` (lazy, ~2.1 MB gz, fetched only on brand-logo use) — and lift chunkSizeWarningLimit above them so the size warning stays meaningful signal instead of a permanent false alarm. App chunk is ~50 KB gz; initial load (app + lucide) ~198 KB gz. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wx5giKP22zuCBmQE2YcPPm
Icon sizing: the .icon svg selector never matched because Lucide/Simple Icons render the <svg> *with* the .icon class (not a child), so Lucide fell back to its intrinsic 24px while Simple Icons svgs (viewBox, no width/height) ballooned to fill the cell. Size the .icon element directly so every source renders in one consistent box. ESLint: flat config (typescript-eslint + react-hooks + react-refresh), `npm run lint` script. Two experimental react-hooks rules fired: - react-hooks/refs on JogStrip/Trackpad's deliberate sync-ref-during-render idiom (latest prop in a stable pointer callback) — disabled that one rule rather than rewrite working input logic; kept rules-of-hooks/exhaustive-deps. - react-hooks/set-state-in-effect on Icon.tsx's SimpleBrandIcon — genuinely cleaned up: resolution is now keyed by name and set only in the async callback, no synchronous setState reset in the effect. lint + tsc clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wx5giKP22zuCBmQE2YcPPm
Two backend-free design surfaces (issue #21 "Both"): Responsive gallery — `npm run dev` then /gallery.html renders the real client in phone / large-phone / 7" / 10"-tablet iframes at once, with layout + orientation selectors, for checking a layout across screen sizes. Dev-only entry (root gallery.html), not in the production build. Reuses the existing demo fixtures via a new DEMO_LAYOUTS export. Ladle — `npm run ladle` serves ButtonGrid / Icon / JogStrip stories in isolation with width/theme controls. Vite-native, Storybook-compatible CSF. .ladle/components.tsx wraps stories in the app's dark surface + CSS. Stories in src/*.stories.tsx. Also: README documents demo mode, gallery, Ladle, and lint under a new "Design tooling" section. tsc + lint clean; production build verified unchanged (dev files excluded). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wx5giKP22zuCBmQE2YcPPm
Text on a button is now opt-in: a widget with a `label` shows it, one without is icon-only. The id remains only as a last-resort fallback when a widget has neither label nor icon, so it isn't a blank unidentifiable button. Replaces the previous `label ?? id` which always showed text. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wx5giKP22zuCBmQE2YcPPm
Inter: self-host Inter (woff2, weights 400/600/700 — the ones the UI uses) via @fontsource, imported by every entry point (app, gallery, Ladle) and set as the primary family ahead of the system stack, for a consistent typeface across devices. Browsers fetch only the ~5 KB latin subset they need. Ladle device sizes: ButtonGrid gains an optional `orientation` prop that overrides the window-based auto-detect (the live app leaves it unset). New Surface stories render the grid in fixed device-dimension frames (phone/tablet, landscape + portrait) so a fixed-size parent simulates a resolution — and, via the override, the portrait transpose — right in Ladle. tsc + lint clean; Inter confirmed loaded (document.fonts.check); production build emits the woff2 subsets and excludes the dev-only stories. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wx5giKP22zuCBmQE2YcPPm
A client-side "Content size" multiplier scales grid content (button icon + label, in-grid jogstrip) on top of the responsive base, so the user can dial readability per device. - settings-store.ts: content-scale setting (min 0.75, max 2.5, step 0.1, default 1.0) with clampContentScale and the same URL>localStorage>default read precedence as the other sliders; key `deckd.contentScale`, param `contentScale`. - App.tsx: set `--content-scale` on the `.surface` layout area only, so grid content scales while the persistent chrome (sibling jogstrip, bottom bar) stays fixed. - style.css: grid-content sizes become `calc(base * var(--content-scale))`; `:root` defaults the var to 1 (keeps chrome and Ladle stories at base); cells get `overflow: hidden` so scaled content clips cleanly. - Settings.tsx: "Content size" slider under Display. Default 1.0 reproduces the current look. Verified: content scales, chrome stays fixed, slider works. tsc + lint clean; 151 daemon tests pass. Design and acceptance criteria in #37; per ADR-0003 React visuals aren't unit-tested (verified via the demo/gallery instead). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wx5giKP22zuCBmQE2YcPPm
Shrink the button label base from clamp(13px,2.4vw,18px) to clamp(11px,2vw,15px) so the icon reads as the primary element with the label as a compact caption. The content-size multiplier (#37) still stacks on top, so anyone wanting larger text can dial it up. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wx5giKP22zuCBmQE2YcPPm
- Reduce button label base again to clamp(9px,1.5vw,12px) — the label is a small caption under the icon; the content-size multiplier still scales it. - The chrome jogstrip "scroll" hint now reads vertically (writing-mode: vertical-rl), fitting the tall narrow strip. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wx5giKP22zuCBmQE2YcPPm
…ize docs
- Reframe from "Linux desktop" to cross-platform: note deckd runs on GNOME
(Wayland), KDE Plasma (Wayland), X11, and macOS (each with its own focus
watcher / input backend). The per-platform setup sections already existed;
the top-line framing was the gap.
- Add a hero screenshot (docs/screenshot.png) — a Firefox layout with icons,
per-button colour, and the persistent scroll strip.
- Document the visual system in "What works today" (bundled Lucide + Simple
Icons via icon: {source, name}, per-button colour) and add the Content
size slider (0.75x-2.5x) to Client tuning + the contentScale URL param.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wx5giKP22zuCBmQE2YcPPm
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.
Implements the design decisions from #21 (grilled) and records them in ADR-0006.
What's here
coloris raw-hex background-only,iconis a{source, name}dispatch resolved against a client-owned renderer registry, one fixed style, per-kind look is hard-coded client CSS, uploads deferred.iconchanges from a bare string to{source, name}on both the daemon (newIconpydantic model) and client (protocol.ts). All 6 shipped layouts migrated. Daemon validates non-empty strings and relays opaquely — it does not enumerate valid sources.Follow-up (tracked in #21, not in this PR yet)
sourcedispatch, unknown-source placeholder) — being added on this branch next.close→x,refresh→refresh-cw, etc.).Also split off #35 (extensible / user-defined control kinds) from the Q4 discussion.
Verification
layouts/files through the new schema).🤖 Generated with Claude Code
https://claude.ai/code/session_01Wx5giKP22zuCBmQE2YcPPm