Skip to content

Design module: component palette, drag and drop, emitted source - #203

Open
pathscale wants to merge 2 commits into
masterfrom
feat/design-module
Open

Design module: component palette, drag and drop, emitted source#203
pathscale wants to merge 2 commits into
masterfrom
feat/design-module

Conversation

@pathscale

Copy link
Copy Markdown
Owner

Implements H6. A Design tab where you compose @pathscale/ui components on
an artboard and read the source they emit, live.

The two decisions H6 said to ask about

What is emitted: one IR, both emitters. document.ts is a tree edited by
pure functions that knows nothing about a target. emit/tsx.ts produces a
plain Solid component importing from @pathscale/ui, pasteable as-is.
emit/layout.ts produces the .layout.tsx and .recipe.ts pair
solid-layouts-library compiles, matching what chuzz already runs in
apps/chuzz/frontend/local-ui. Both are on screen behind a toggle. A third
target is a file and a line.

The two differ structurally, and the difference is not cosmetic: a layout
template has exactly one root element because slot.root has to land
somewhere, so it always wraps, while the TSX emitter returns a lone dropped
Button bare.

Where artboards render: the same document. Hit testing is
event.target.closest("[data-design-id]"), so no bridge is involved, the
control socket sees every node, and ps-qa can address each one by id. The
canvas takes pointer events in the capture phase and stops them there, which
is what lets a click select a real Button instead of pressing it, with no
pointer-events: none layer that would have broken the hit test it was
meant to serve.

The metadata gap

dist/layouts.manifest.json gives a name and a kind per component and
nothing a properties panel can read, so catalog.ts curates 22 entries by
hand: 7 layout, 9 display, 6 form. It is shaped like the recipe metadata the
library already carries, and each entry records the recipe it was
transcribed from, so a generator can replace it wholesale. Two notes for
whoever writes that generator, both learned here:

  • Two recipe dialects exist upstream. Newer components call
    recipe({ props, defaults }); older ones export a CLASSES const whose
    nested keys are the same axes. It has to read both.
  • The manifest is not an import list. Kbd is in it and is not exported
    from the package root. A curated entry for it typechecked as metadata and
    failed to compile the moment the canvas tried to render it, so it was
    dropped. Generating from the manifest alone would emit imports that do not
    resolve; cross-check dist/index.d.ts.

Verification

Per docs/ui-verification.md, the emitted source is asserted as text, which
is the deliverable and needs no renderer: dragging a Button onto an empty
canvas emits exactly the import and the element, and that is a string
comparison.

  • bun run typecheck clean
  • bun run lint clean, including both contract gates
  • bun run test:run: 426 passed, 44 files, 32 tests new
  • bun run build succeeds

Everything drives by accessible name as well as by pointer, so the palette,
the property rows, the target switch and the emitted source are all
reachable from ps-qa. The .ron outcomes for the Design tab are not in this
PR; the ui-control gate does not require them, because every input primitive
rendered on the canvas is one AgencyZero already imports and already covers.

Files outside features/design/

The ownership table gives this package features/design/ only. Five edits
land elsewhere, each the same shape the Analytics tab already uses, and each
one line or one block: the TabKind union, its icon in TAB_ICON, the pane
Match, the openDesign action, and the strip button. Plus the two i18n
catalogues, appended to.

Not done

No layers tree, no multi-artboard, no persistence: the document lives for
the life of the tab. No prop metadata generator, which by H6's own reading
belongs in UI/ and solid-layouts and needs its owner's agreement.

meh added 2 commits August 31, 2026 20:55
A Design module for composing @pathscale/ui components and reading the
source they emit.

One IR, two emitters, which was the H6 decision. `document.ts` holds a
tree edited by pure functions and knows nothing about a target;
`emit/tsx.ts` produces a plain Solid component importing from
@pathscale/ui, and `emit/layout.ts` produces the `.layout.tsx` and
`.recipe.ts` pair solid-layouts compiles, matching what chuzz already
runs in `apps/chuzz/frontend/local-ui`. Adding a third target is a file
and a line.

Artboards render in the app's own document, the other H6 decision. Hit
testing is `closest("[data-design-id]")`, so no bridge is involved, the
control socket sees every node, and ps-qa can address them by id. The
canvas takes pointer events in the capture phase and stops them there,
which is what lets you select a real Button instead of pressing it.

`dist/layouts.manifest.json` carries a name and a kind per component and
nothing a properties panel can read, so `catalog.ts` curates 22 entries
by hand. It is shaped like the recipe metadata upstream already has, and
each entry records the recipe it was transcribed from, so a generator can
replace it wholesale later. That generator has to land in UI and
solid-layouts, outside this package. It will also need to cross-check
`dist/index.d.ts`: the manifest is not an import list, and Kbd is in it
without being exported from the package root.

Everything drives by accessible name as well as by pointer, so the whole
module is reachable from ps-qa. Tests assert the emitted source as text,
since the source is the deliverable: one dropped Button emits exactly the
import and the element.
The pencil beside the gauge opens Design the same way the gauge opens
Analytics: one tab, reused if already open.

Five small edits outside `features/design/`, each the same shape the
Analytics tab already uses: the `TabKind` union, its icon, the pane
switch, the `openDesign` action, and the strip button.
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