@braidhq/studio
v0.4.2
Published
Web UI for Braid.
Readme
@braidhq/studio
Braid keeps a product's intent and its code aligned in one knowledge graph. @braidhq/studio is the web UI a human uses to see that graph and steer it. It renders the graph, the review queues that gate every change to it, and the skill runs that produce those changes, talking to the server over REST and a live event stream.
Role
Studio is the presentation layer. It reads and displays server state, and it turns a reviewer's decisions into API calls, but it holds no authority of its own.
- The Surfaces: The graph canvas and table, the Proposals and Clarification review queues, the Actions and Batch skill panels, Activity, and History, one page per workspace surface.
- The Live Loop: A server-sent event stream that invalidates cached queries as runs finish and proposals apply, so the UI reflects server state without polling.
- The Advisory Gate: A client-side policy layer that disables and hides what a viewer may not do, mirroring the server's rules for a quiet UI while the server stays the real enforcer.
Structure
The source is grouped by kind, not by feature. Pages compose components, components read through hooks in lib, and policy gates them.
src/
├── main.tsx boot: server URL, tokens, then mount
├── App.tsx auth gate, layout, routing, context providers
├── lib/ server client, data hooks, auth, routing state
├── pages/ one surface per file, plus settings/
├── components/ app widgets, graph/, ui/, SkillTranscript/
└── policy/ client-side capability checks- lib: The non-visual core. The REST client (
api.ts), the SSE streams (sse.ts,useWorkspaceEvents.ts), the TanStack Query hooks (queries.ts), the run store that outlives page mounts (runStore.ts), per-remote auth and tokens, and the hash-routing state. - pages: One file per surface, fed a
workspaceId. Every page is workspace-scoped exceptLoginandSettings. - components: App-level widgets, the
graph/canvas and table layer over@xyflow/reactanddagre, theSkillTranscript/renderer for a liveSkillEventstream, and theui/shadcn and Radix primitives. - policy: The capability registry and checks a component asks through
useWorkspacePolicy, kept in lock-step with the server's own resolution.
Data Flow
State comes from the server and returns to it as intent. Studio caches and renders in between, and never mutates the graph on its own.
- Reads: TanStack Query fetches through
api.ts. A per-workspace SSE subscription invalidates the matching query keys as events arrive, so a finished run or applied proposal refreshes the view. - Writes are proposals: The canvas never edits the graph directly. A skill run emits a
Proposal, a reviewer sees its diff and validation issues, and applying it POSTs to the server, which commits and emits the event that refreshes the graph. - Runs stream:
runStoreholds in-flight and completed runs outside React state, so leaving and returning to a page keeps the transcript and lets a multi-turn conversation resume.
Design
Studio follows Linear: dark-first, compact, keyboard-first, with a purple accent. The look lives in tokens, so src/styles.css is the single source of truth for every colour, radius, and size. This section is the guideline, not the numbers.
- Tokens, Not Values: Style with semantic tokens (
bg-background,bg-card,text-muted-foreground,border-border) and the named type scale. Never inline a hex, anoklch(...), or an arbitrarytext-[13px]. A missing value is added tostyles.css, not hardcoded in a component. - Type: Geist for the UI, Geist Mono for identifiers, ids, paths, args, and skill names. Four sizes only:
text-2xsfor labels and metadata,text-xsfor body,text-smfor emphasis,text-basefor headers. An id takes its size from how dense its surroundings are, not from being an id:text-2xsin a row, a footer, or inline, andtext-xsonly when it stands alone. - Density: An 8px rhythm. The sidebar recesses below the page, cards lift above it, and rows breathe. Dense like a tool, never cramped.
- Icons: lucide-react only. Studio runs denser than shadcn's
size-4default, and 12px carries most of the UI. An icon sits one step below its label, sotext-2xstakessize-2.5andtext-smtakessize-3. Override on the button, not the icon, so the rule survives whatever a caller passes. - Casing: Title Case for buttons, tabs, and any short phrase naming an action, UPPERCASE for small section labels, Sentence case for descriptions, placeholders, and full-sentence hints, and lowercase for status text and for fragments that interpolate an id, since Title Case there yields
Unknown node Reference. - Reuse the Patterns: A selectable row shows a 3px purple bar on its left edge when active (
ListRow). Empty states go throughEmptyState, domain status throughStatusBadge, and a new primitive comes from shadcn inui/before it is hand-rolled. - Restrained Motion:
transition-colors duration-150on hover,tw-animate-cssfor enters, and nothing that bounces, scales, or parallaxes. Reduced-motion is honoured globally. - Accessibility: A visible focus ring, a pointer cursor on every control, and an
aria-labelon icon-only buttons. The same code ships to the Tauri desktop and mobile shells, so keep touch targets near 44px where the layout allows. - No Em-Dashes: Never
—or–in a user-facing string. Use a colon, a comma, or two sentences. - Typographic Marks: One ellipsis character
…for a truncation or a busy label, never three periods, since the character keeps its spacing and never breaks across a line.→is reserved for a directed relationship, such as an edge's two ends or a pointer at a target.
Boundaries
These are the rules that keep Studio a thin, honest client. They are enforced in review.
- Schema Only: Studio imports
@braidhq/schemaand nothing else from the monorepo. It never reaches intocoreorserver. - The Server Decides: Client policy gates the UI for a calm experience, but the server enforces every rule. A rejected request surfaces as an error or a return to login, never a silent success.
- No Direct Mutation: Every graph change flows through a proposal a human applies. There is no path from a canvas gesture to a committed edge.
- URL Is the Route: Hash-based routing holds the active workspace and surface. There are no query params, and navigation replaces rather than stacks history.
- Per-Remote Auth: Each server has its own bearer token, in web
localStorageor the Tauri keyring. Switching the active remote clears the query cache so no stale data bleeds across.
Dependencies
Studio sits at the edge of the monorepo, downstream of the schema and the running server.
- Depends On:
@braidhq/schemafor every shape it renders, a live@braidhq/serverto talk to, and the UI stack:react,@tanstack/react-query,@xyflow/reactwithdagre,mermaid,react-markdown,cmdk, Radix andtailwindcss, and@tauri-apps/apifor the desktop shell. - Consumed By: The
desktopTauri shell and@braidhq/server, both of which serve the built assets this package publishes, and any browser pointed at a Braid server.
