@0gfoundation/design-collab
v0.3.4
Published
Designer/front-end collaboration infrastructure: per-commit Storybook, an aggregated design gate, and branch sync that never hands a designer a conflict.
Keywords
Readme
@0gfoundation/design-collab
Infrastructure for designer ↔ front-end collaboration in a shared codebase: a per-commit Storybook, one aggregated design gate, and branch sync that never hands a designer a merge conflict.
Every check here is a mechanism with no project knowledge in it — "a View
must have a sibling story", "a generated artifact must match its source",
"these identifiers must not reach a production bundle". Everything about your
project arrives through design-collab.config.mjs. That split is the whole
point: the mechanisms are portable, the paths are not.
Zero runtime dependencies. Node ≥ 20.
中文的完整接入指南:
ADOPTING.zh-CN.md— whatinitreads and what it refuses to guess, how to read a gap, and what each of the twelve checks needs before it can tell you anything.
Getting started
pnpm add -D @0gfoundation/design-collabPublic on npmjs, so there is nothing else to configure — no .npmrc, no
registry token in CI, no environment variable in your deploy platform. Then:
design-collab init # writes the config; run it again to scaffold
# the agent skill and the CI workflow
design-collab install-drivers # once per clone — see "Merge drivers" below
design-collab start # before each piece of design workinit runs in two passes on purpose. Without a config there is nothing to fill
the templates with, so the first pass writes only the config and stops. Once you
have edited it, the second pass generates the skill and the workflow from your
values, so the three artifacts cannot disagree with each other.
The first pass reads your repository rather than copying an example: where the
app lives, your source root, the path aliases in tsconfig.json, your typecheck
script, the base branch from origin/HEAD, and whether you have Storybook or an
i18n library at all. It prints what it found and what it could not, and writes
the reasons into the file next to each value. Anything it could not observe is
absent rather than guessed — the package's defaults then apply, and the gap
stays visible.
One field is always yours: writableSet is written empty and an empty set is
rejected at load time. Which paths a designer owns is a decision, not a property
of your tree.
Adoption is not silent
A check that cannot run does not report as one that passed. Three things used to look identical in the output and now do not:
| | Fails the run? |
|---|---|
| declared — you wrote checks: { 'message-parity': false } | no; it is an answer |
| n/a — nothing relevant changed in this run | no |
| gap — enabled, and it could not run | yes |
A gap is either a check whose configuration is missing, or one that ran and
examined zero subjects. The second used to be the dangerous one: with globs
describing a different repository, view-stories reported PASS — 0 file(s)
covered and design-wiring reported PASS — no source files under src. The
summary line then said "all N check(s) passed", because it counted only the
checks that ran.
So the summary now states every state at once —
[design-collab] 14 passed, 2 failed, 3 n/a.— and each gap is listed with the one sentence that says what to do about it. Configure it, or declare it inapplicable. Both are answers; leaving it silent is not.
Commands
| | |
|---|---|
| start | Preflight before writing code: branch topology, lineage, merge drivers, PR-body scaffold. |
| check | Every gate CI runs, locally, reporting all failures at once. --with-storybook for the exact CI bar. |
| prepare | Write what Storybook reads: the merged message catalogue and the three manifests. --skip-manifests for an app build, which wants the catalogue and nothing else. |
| sync | Bring a design branch up to date — or stop cleanly, leaving the tree untouched. |
| install-drivers | Wire the git merge drivers into this clone. |
| sb | Resolve a commit to the Storybook that was built from it. |
| init | Scaffold config, agent skill and CI workflow. |
sync, and why it exists
A gate that detects a conflict and stops there has moved the problem onto the person least equipped for it. The usual escalation — check out the integration branch, merge, resolve, push — is not something a designer can or should do.
design-collab sync rebases onto the base. If it cannot do so cleanly it
aborts and leaves the branch exactly as it was — never a tree full of
conflict markers — and prints a hand-off message naming each conflicting file
and who last changed it on the base. The designer keeps designing; someone with
front-end context gets a specific request.
It rebases rather than merges because a design branch that merges its base
absorbs that line's history, and then conflicts against lockfiles and CI config
its author never opened. check enforces that with a lineage check.
Merge drivers
Most "conflicts" in a design workflow are not disagreements:
- i18n catalogs — two people adding two different keys. Merged per key.
- Design tokens — two people adding two different custom properties. Merged per declaration.
- Generated artifacts — recomputed from source instead of merged, because reconciling a derived file by hand is doing what a command does correctly.
On a genuine conflict all three keep the file valid and parseable rather than writing conflict markers into it, and name exactly which keys or declarations need a human.
install-driversmust run once per clone..gitattributesmaps paths to a driver and is committed; the driver command is local git config and cannot be — git refuses to execute a command that arrived with a repository, which is a security property, not an oversight.startchecks for it and tells you.
Configuration
design-collab.config.mjs at the repo root. See
design-collab.config.example.mjs — it is
kept complete on purpose and doubles as the schema documentation.
The required fields are appRoot and writableSet. writableSet has no
default because an empty set would reject every change and a permissive one
would make the path guard decorative.
Disable any check by name: checks: { 'view-stories': false }.
One caveat about globs
The glob implementation here treats (, ), [ and ] as literals, so
character classes and alternation are unavailable. That is deliberate: Next.js
route directories are named (app) and [locale], and a general-purpose glob
reads [locale] as a character class and silently fails to match the single
most important path in the reference project.
Storybook
withDesignCollab() wraps a Storybook config to tag stories changed since the
base, seed the manifests the manager imports statically, and — for Tailwind v4 —
rewrite @theme to @theme static so the Foundations page shows live token
values instead of a stale snapshot.
If your tokens are split across files pulled in with @import, the rewrite must
inline them first: Tailwind v4 resolves @import itself, so an imported file
is never handed to the bundler as its own module and a transform will never see
it. Matching the imported filenames does not help. The template main.ts shows
the working shape.
withDesignCollab() only GUARANTEES the manifests exist — Storybook does not
await main.ts, so it seeds empty ones rather than blocking startup on a git
diff. Re-deriving them is prepare's job, in front of Storybook:
"storybook": "design-collab prepare && storybook dev -p 6006",
"build-storybook": "design-collab prepare && storybook build",
"build": "design-collab prepare --skip-manifests && next build"Do not copy a manifest generator into your project's scripts/. That is what
the reference project did, and the result was ~1200 lines living in two places
with nothing comparing them — a fix to one copy silently did nothing for the
other.
Tests
node --test test/The merge-driver and sync tests run against real git repositories. What
those units promise is a behaviour of git merge and git rebase, and a mock
can only confirm that the code calls git the way the test author imagined. Each
driver test also asserts a baseline — that the same scenario conflicts
without the driver — because otherwise a green suite cannot distinguish "the
driver works" from "nothing ever collided".
