Skip to content

Rebuild the Studio as a single-voice Liquid Glass app - #3

Open
MattModeCode wants to merge 2 commits into
mainfrom
feat/liquid-glass-studio
Open

Rebuild the Studio as a single-voice Liquid Glass app#3
MattModeCode wants to merge 2 commits into
mainfrom
feat/liquid-glass-studio

Conversation

@MattModeCode

@MattModeCode MattModeCode commented Sep 9, 2026

Copy link
Copy Markdown
Owner

The Studio worked, but it looked like a local dev tool: two columns, bundled
mono type everywhere, a flat dark background, and a voice picker you had to
answer every time despite there only ever being one voice that mattered.

This rebuilds the interior. The desktop shell, the engine, and all eight
/api/* routes are untouched — nothing here changes what Double Chin does,
only what it is like to use.

The interface

Single voice. The app opens straight into composing. The voice is resolved
at boot and never appears as a choice on the main surface; enrolment, switching
between enrolled voices, and the environment report all live in a Settings
sheet. The choice persists in localStorage, so it stops depending on
whichever voice happens to sort first. The toolbar shows the voice rather than
repeating the window title.

Progressive disclosure. At rest: a script editor and a Generate button.
Delivery controls and the prosody reference sit behind panes that summarize
their own state when closed — "Default", or "Expression 0.70 · fixed seed".

Apple's design system, not an impression of it. The first pass wore Liquid
Glass without earning it: every panel was translucent, and behind them sat an
animated four-pool radial gradient with a noise overlay — a synthetic backdrop
invented so the blur would have something to show. That is exactly the tell.
Apple's materials blur the app's own content; a gradient placed behind glass to
make it look like glass is decoration pretending to be a material.

So the ambient field, the grain and the drift animation are gone, and what is
left is Apple's grouped-content system: a recessed page (#F2F2F7 / #1C1C1E)
with opaque cards on top (#FFFFFF / #2C2C2E), divided by .5px separators
and carrying no shadow, because grouped cards in AppKit don't have one.

Glass now appears in the two places Apple puts it, and only there:

  • the toolbar, which the page genuinely scrolls beneath
  • the settings sheet, which sits over the page

Both blur real content.

Colour and type are semantic AppKit/UIKit values — label, secondaryLabel,
tertiaryLabel, separator, the fill ramp, controlAccentColor, systemGreen /
Orange / Red — at the macOS scale (13px body, 11px secondary, 15/17px titles).
Two departures, documented in the file: the secondary label alpha is raised
because Apple's own value measures 3.5:1 on white and fails WCAG AA, and the
accent has a defined pressed shade instead of a brightness filter.

Controls use AppKit metrics: 36px prominent button, 24px secondary, 8px
control radius, 12px card radius, the three-point accent focus halo, a
disclosure triangle that rotates 90°, and a slider whose track is a two-stop
fill. History is one inset grouped list with hairline dividers rather than
eight floating cards. Nothing pulses, glows or drifts.

I also tried real window vibrancy — pywebview 6.2.1 does give a genuine
NSVisualEffectView behind the WebView with transparent=True, vibrancy=True
— and did not ship it: it costs the window shadow, which is a bigger Mac
regression than desktop-sampling translucency is a win.

Details. ⌘↵ generates (scoped so it does not fire behind the Settings
sheet). Reduced motion, increased contrast with its own dark variant, visible
focus rings, labelled controls, aria-live on status regions, and no
horizontal scroll down to 520px.

Structure

static/ was three files and is now six: tokens.css (design system),
style.css (components), and ES modules api.js (endpoint wrapper),
format.js (pure presentation helpers), app.js (state and wiring). The
bundled JetBrains Mono files are deleted — system type for prose, ui-monospace
for numerics.

Desktop shell

The native window painted pywebview's white default before the page loaded — a
bright flash on the way into a dark interface. It now launches on the
interface's own background colour, read from the macOS appearance, and the
default size follows the content column (820×860). Since that colour lives in
two files, a test asserts desktop.py and tokens.css cannot drift apart.

Fixes found while building

  • History rows rendered right-to-left: .score carried an explicit grid-row,
    which took it out of auto-flow and let it claim column 1.
  • A single-chunk take sat at 0% progress for its whole run — the bar was showing
    "chunks finished", which is zero until the only chunk lands.
  • <dialog> lost its centring to the global * { margin: 0 } reset.
  • "1 chunks".
  • Re-enrolling an existing voice always failed with a 409; the form had no
    overwrite field even though the API has always accepted one.
  • Increased contrast whitened the glass without redefining --ink, so dark
    mode plus increased contrast was white-on-white. (Found by /code-review.)

Test plan

  • pytest — 110 passed, 2 skipped (3 new tests for the launch colour)
  • Browser at 1120px and 560px, light and dark, no console errors
  • Real pywebview window in both system appearances
  • Rebuilt dist/Double Chin.app — styling survives PyInstaller, and the
    whole static/ directory is already in the spec's datas, so no
    packaging change was needed
  • End-to-end generation: idle → progress → verifying → result (0.855,
    strong match), plus replay from history
  • Contrast measured against the shipped tokens

Not included

The README prose is updated, but demo/studio/double-chin-studio-demo.gif and
the linked session video still show the old interface. Re-recording them is a
separate pass.

https://claude.ai/code/session_01FdWprv4TKfVzapYFJnRffz

The Studio was a two-column web page in bundled mono type on a flat dark
field — functional, but it read as a local dev tool rather than a Mac app,
and it made you pick a voice every time despite there only ever being one
that mattered.

Interface
- Single column built around one voice. It is resolved at boot and never
  appears as a choice on the composing surface; enrolment, switching and
  the environment report moved into a Settings sheet.
- Progressive disclosure: script and Generate at rest, delivery controls
  and prosody marks behind panes that summarize themselves when closed.
- Apple Liquid Glass material — layered translucency with backdrop blur
  and saturation, specular top lip, depth shadows — over an ambient colour
  field that gives the glass something to refract.
- Light and dark are both first class, each with its own glass and ink
  values rather than one palette dimmed. Increased contrast drops the
  translucency entirely and has its own dark variant.
- System UI type for prose, mono kept only for numerics. The bundled
  JetBrains Mono files are gone; nothing referenced them any more.
- ⌘↵ generates. Reduced motion, focus rings and labelled controls throughout.

Structure
- static/ split into tokens.css (design system), style.css (components),
  and ES modules api.js / format.js / app.js.
- The eight /api/* routes are untouched; this is a front-end change.

Desktop shell
- The window painted pywebview's white default before the page loaded,
  which flashed on the way into a dark interface. It now launches on the
  interface's own canvas colour, chosen from the macOS appearance, with a
  test asserting the two files cannot drift apart.

Verified in the browser, in the pywebview window, and in a rebuilt
dist/Double Chin.app; 110 tests pass.

Claude-Session: https://claude.ai/code/session_01FdWprv4TKfVzapYFJnRffz
…ve field

The first pass wore Liquid Glass without earning it. Every panel was
translucent, and behind them sat an animated four-pool radial gradient with a
noise overlay — a synthetic backdrop invented so the blur would have something
to show. That is the tell: Apple's materials blur the app's own content, and a
gradient placed behind glass to make it look like glass is decoration
pretending to be a material.

Backgrounds
- The ambient field, the grain overlay and the drift animation are gone.
- Apple's grouped-content system instead: a recessed page (#F2F2F7 / #1C1C1E)
  with opaque cards on top (#FFFFFF / #2C2C2E), divided by .5px separators
  and carrying no shadow, because grouped cards in AppKit don't have one.

Materials
- Glass now appears in exactly the two places Apple puts it: the toolbar,
  which the page genuinely scrolls beneath, and the settings sheet, which
  sits over the page. Both blur real content.

Colour and type
- Semantic AppKit/UIKit values throughout — label, secondaryLabel,
  tertiaryLabel, separator, the fill ramp, controlAccentColor, systemGreen /
  Orange / Red — rather than invented ones. Two documented departures: the
  secondary label alpha is raised because Apple's own value measures 3.5:1 on
  white and fails AA, and the accent has a defined pressed shade instead of a
  brightness filter.
- macOS type scale: 13px body, 11px secondary, 15/17px titles.

Controls
- AppKit metrics: 36px prominent button, 24px secondary, 8px control radius,
  12px card radius, the three-point accent focus halo, a real disclosure
  triangle that rotates 90 degrees, and a slider whose track is a two-stop
  fill rather than a gradient.
- History is one inset grouped list with hairline dividers, not eight
  floating cards.
- The primary button lost its coloured glow; nothing pulses or drifts.

Window
- The toolbar was repeating the window title. It now shows the voice, which
  is information the title bar doesn't already carry.
- Default size follows the content column: 820x860.
- Launch colour tracks --bg-grouped; the drift test follows the rename.

Claude-Session: https://claude.ai/code/session_01FdWprv4TKfVzapYFJnRffz
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