From e6f3f1f31ba922c37b6110e56c8077a8f6965fe6 Mon Sep 17 00:00:00 2001 From: Igor Samokhovets Date: Sun, 19 Jul 2026 19:24:20 +0200 Subject: [PATCH] internal: migrate Studio styling to Tailwind CSS --- README.md | 6 + packages/studio/client/src/main.tsx | 320 ++++++++++----- packages/studio/client/src/studio.css | 541 ++++---------------------- packages/studio/client/src/ui.tsx | 33 ++ packages/studio/package.json | 2 + packages/studio/vite.config.ts | 3 +- pnpm-lock.yaml | 358 ++++++++++++++++- 7 files changed, 702 insertions(+), 561 deletions(-) create mode 100644 packages/studio/client/src/ui.tsx diff --git a/README.md b/README.md index ce85c2d..1c13440 100644 --- a/README.md +++ b/README.md @@ -251,6 +251,12 @@ pnpm bench:callback # release callback work-budget benchmark pnpm validate:release # standard validation plus callback benchmark ``` +### Studio styling + +Studio uses Tailwind CSS v4 through the official Vite plugin. `packages/studio/client/src/studio.css` is configuration-only: it imports Tailwind, registers the React TSX source tree, and defines semantic `studio-*` theme tokens and the existing responsive breakpoints. All UI styling belongs in complete, statically detectable utility strings in `packages/studio/client/src/**/*.tsx`; shared utility-only primitives live in `ui.tsx`. + +Do not add authored selectors, `@apply`, `@layer`, `@media`, or `@utility` rules to `studio.css`, and do not assemble utility names with string interpolation. Map dynamic UI states to complete class strings instead. Tailwind Preflight is enabled, so form controls must also carry their intentional utility styling. Validate changes with `pnpm --filter @patchwave/studio build` and `pnpm --filter @patchwave/studio test`. + The callback does not allocate, lock, parse, log, block, invoke JavaScript/N-API, or control the stream. Output is sanitized for non-finite values and bounded by a final safety guard. ## Experimental scope diff --git a/packages/studio/client/src/main.tsx b/packages/studio/client/src/main.tsx index 9d79923..9097cd6 100644 --- a/packages/studio/client/src/main.tsx +++ b/packages/studio/client/src/main.tsx @@ -7,11 +7,20 @@ import type { PatchFieldPath, PatchScalar, SourceBinding, + StudioDocumentPhase, StudioDocumentSnapshot, StudioEditResult, StudioServerMessage, } from "../../src/index.js"; import { shouldHandlePerformanceKey } from "./keyboard.js"; +import { + cx, + eyebrowClass, + panelHelpClass, + sourceBadgeClass, + statusPillClass, + StudioButton, +} from "./ui.js"; import "./studio.css"; type RuntimeState = any; @@ -20,6 +29,49 @@ type Selection = { index?: number; }; +const phaseToneClasses: Record = { + ready: "text-studio-text-phase", + writing: "text-studio-text-phase", + previewing: "text-studio-warning", + "source-written": "text-studio-info", + reloading: "text-studio-info", + "audio-accepted": "text-studio-success", + conflict: "text-studio-danger", + error: "text-studio-danger", +}; + +const sourceBadgeToneClasses: Record = { + literal: "text-studio-text-panel", + computed: "text-studio-warning-muted", + default: "text-studio-info-muted", +}; + +const serialConnectorClass = + "after:absolute after:top-1/2 after:-right-2.75 after:h-px after:w-2.75 after:bg-studio-connector after:content-['']"; + +const parameterClass = "block border-b border-studio-border-parameter py-3.75"; +const parameterLabelClass = "mb-2.25 flex items-center justify-between text-studio-copy font-bold"; +const nativeSelectClass = + "w-full appearance-auto [background-color:revert] [border-radius:revert] [border:revert] [color:revert] [padding:revert]"; +const noteClass = + "mt-3.5 rounded-studio-note border border-studio-note-border bg-studio-note-bg p-2.5 text-studio-copy leading-[1.5] text-studio-note"; + +const keyboardKeys = [ + { label: "A", raised: false }, + { label: "W", raised: true }, + { label: "S", raised: false }, + { label: "E", raised: true }, + { label: "D", raised: false }, + { label: "F", raised: false }, + { label: "T", raised: true }, + { label: "G", raised: false }, + { label: "Y", raised: true }, + { label: "H", raised: false }, + { label: "U", raised: true }, + { label: "J", raised: false }, + { label: "K", raised: false }, +] as const; + function requestId(): string { return crypto.randomUUID(); } @@ -194,34 +246,43 @@ export function App() { }, [patch?.source?.oscillators?.length, patch?.source?.filter, patch?.effects?.length]); return ( -
-
+
+
-

PATCHWAVE

-

Studio

+

PATCHWAVE

+

Studio

-
- - - + + {connected ? "Connected" : "Disconnected"}
-
-