lecodes-design
v0.3.1
Published
LeCodes Design — a live canvas of app screens for AI-driven prototyping. Dev server + browser board, launched via `lecodes design`.
Readme
lecodes-design
LeCodes Design — a live canvas of app screens for AI-driven prototyping. An intent-capture
layer: each screen on the canvas is an executable LeCodes render (real code on mock data), the
edges between them are drawn navigation intent, and the whole folder is the spec an AI builds the
real app from. See lecodes-design-concept.md at the repo root for the full concept.
Launched through the CLI — this package is the dev server + the browser board. It works inside a cloned lecodes project or standalone in any folder (no login or server project needed):
npm i -D lecodes-design # alongside lecodes-cli
lecodes design init # scaffold design/ (screens/, shared/, meta.json, CLAUDE.md)
lecodes design # start the dev server + open the canvas
lecodes design snapshot # headless JSON/PNG of every screen (Claude's eyes)How it works
design/
screens/login.ts ← one file = one screen; default-exports UIScreen([...]); islands
shared/tokens.ts ← design tokens + reusable components (only allowed import)
meta.json ← the human-owned graph: positions, descriptions, edges
CLAUDE.md ← the conventions, written for the AI- The dev server (
src/server, zero runtime deps) watches the design folder, compiles each screen on demand through the CLI's pipeline (chisel + the vendored SDK — the CLI injectscompileScreen), serves the project files forasset()urls, and pushes SSE change events. - The board (
src/app, Vue 3) renders every screen live via lecodes-renderer (canvas-ui): each tile is its own creator-ui WASM instance +<canvas>, executing the compiled bundle with_creatorUIbound to it — the browser twin of the renderer's headless entry. Pan/zoom the board (wheel / two-finger scroll = pan, ctrl+wheel / pinch = zoom, middle-button drag = pan from anywhere — even over a screen, double-click = fit, bottom-right cluster: ▤ concept, ⚑ badges, ⇥ arrange, ◎ hints, −/+/fit, click the % to reset to 100%); the dot grid lives in world space; edges draw as labeled arrows. - Tiles follow a select-to-interact model: an unselected tile is a solid object — drag it
anywhere (not just the header) to reposition it (the release writes
posintometa.json), and a plain click selects it. A selected tile (blue ring) is a working prototype: the body forwards pointer input (buttons, scroll, router, drags), and focusing an in-screen input shows the simulated keyboard band + captures real keystrokes. Arrow keys nudge the selected screen (Shift = ×10), the header ↻ restarts its prototype state, Escape / clicking the background deselects. The header always drags, selected or not. - The flow graph is edited on the canvas too: drag the connect handle (right edge of a tile,
on hover) onto another screen to draw an edge; click an edge (or its label) to edit its
label/activator or delete it — the activator field offers a dropdown of the source screen's
live named elements; click a tile's header note to edit the screen description.
Everything writes back into
meta.json— the file Claude reads as the app map. Edges between the same pair of screens fan out instead of overlapping. - Auto-arrange (the ⇥ button, or
lecodes design arrange) re-lays-out the flow graph as columns reading left→right (a small deterministic Sugiyama layout), so edges stop crossing and diving under tiles. It only writes when the result beats the current layout — a hand-arranged board is left alone — tiles glide into place, and a toast offers one-shot Undo. A screen with noposis already auto-placed next to its flow neighbours on the board (computed live inreadDesignState, never written) — so the AI adds screens with edges but no coordinates and they land sensibly.lecodes design arrange --newjust commits those positions to meta.json (placing only the position-less screens, never moving the rest). Seeauto-arrange.md. - Groups carve the board into named sections (Onboarding, Checkout…). Membership lives on the
screen (
"group"in its meta entry — the AI sets it as it creates screens); each group draws as a tinted region with a label chip you can drag to move the whole group, ✎ to rename, ✕ to dissolve, and click to focus (dimming the rest). A selected tile's header gets a chip to change its group. Arrange treats each group as a sub-flow and lays the groups out left→right by their cross-group edges. Seegroups.md. - Text is edited on the canvas: double-click any rendered text to edit it in place — the
edit rewrites the string literal in the screen's source file (Enter commits, Escape
cancels, Shift+Enter inserts a newline). This works through the SDK's creation-site capture +
the bundle's source map, so it lands exactly where
UIText(...)was called — including throughshared/component wrappers. Only literal strings are editable: text computed at runtime (interpolation,node.text = …) is declined with a notice instead of guessed at. - Images are replaced on the canvas: double-click an image to pick a file, or drag an
image file from your OS onto it (the target highlights). The upload is stored under
design/assets/(names deduped; byte-identical files reused) and the image's source reference is rewritten — an inlineasset("…")argument, an asset held in a variable (const man = asset("…")), a hand-writtenimport man from "../assets/man.png", or an asset exported from ashared/file; a remote-URL literal is converted to a localasset()reference. The tile previews the new image instantly; dynamic or ambiguous sources are declined safely. - Hint mode (the ◎ button or
H) outlines what the canvas can do something with, on every tile: interactive elements in blue, in-place-editable texts in green, replaceable images in purple (a legend appears next to the controls). - Concept panel (the ▤ button or
C) docks a read-only reader ofspec.md— the narrative half of the design (data model, actors, rules & decisions) — beside the screens, so the developer can review the decisions a backend gets generated from without leaving the board. It renders the markdown (headings, tables, lists, code) with a table of contents; every[[screen-id]]link becomes a chip that focuses that screen on the board ([[id@state]]also switches the tile's state), and a broken[[link]]is flagged (with a count on the ▤ button). The reverse direction: a selected screen shows an "in concept · N" chip that opens a compact popover of the exactspec.mdlines mentioning it (the per-screen "why does this exist / what rules touch it" view) — each line jumps the panel to that exact mention, and a footer opens the panel to cycle them. When a design has nospec.md, the panel offers a one-click Create spec.md (same scaffold aslecodes design init). Editing stays in the file — the panel is read-first. A companion toggle (⚑) shows a non-blocking dot on screens with no role tagged. Both are off-by-nothing: everything is toggleable. - Edges and their activators cross-highlight: selecting or hovering an edge lights up the element its activator names on the source screen (amber pulsing ring + ⚡ tag — dropdown options preview live while the editor is open); hovering that element on the tile lights the edge(s) up in return, so the flow graph reads from either end.
- Screens hot-reload: edit
screens/x.ts→ only that tile recompiles and re-runs; editshared/→ all tiles reload; editmeta.json→ positions/edges update; editspec.md→ only the concept panel refreshes (no screen recompiles).
Because the same render tree drives the board, lecodes design snapshot (JSON/PNG, via
lecodes-renderer headless), and the future app — what Claude "sees" is what you see.
Development (of this package)
bun run build # vite build (dist/app) + server bundle (dist/server.js)
bun run typecheck # vue-tsc
bun run dev # vite dev server for the board, proxying to a running design serversrc/app/wasm/ holds a copy of creator-ui's emscripten glue + wasm (same pattern as the viewer);
re-copy from lecodes-renderer/src/layout/creatorUi/ when the layout engine is rebuilt.
