basis-design 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/LICENSE +177 -0
  2. package/NOTICE +7 -0
  3. package/README.md +135 -3
  4. package/bin/basis.js +6 -0
  5. package/index.html +16 -0
  6. package/package.json +66 -4
  7. package/preview.html +12 -0
  8. package/server/agent.ts +845 -0
  9. package/server/brief.ts +355 -0
  10. package/server/chatlog.ts +57 -0
  11. package/server/exec.ts +77 -0
  12. package/server/export.ts +180 -0
  13. package/server/guidelines.md +379 -0
  14. package/server/history.ts +107 -0
  15. package/server/index.ts +414 -0
  16. package/server/paths.ts +57 -0
  17. package/server/previews.ts +71 -0
  18. package/server/project.ts +151 -0
  19. package/server/projects.ts +213 -0
  20. package/server/reach.ts +192 -0
  21. package/server/rebase.ts +46 -0
  22. package/server/reviews.ts +156 -0
  23. package/server/setup.ts +219 -0
  24. package/server/snapshots.ts +67 -0
  25. package/server/workspace.ts +423 -0
  26. package/shared/annotations.ts +43 -0
  27. package/shared/contracts.ts +190 -0
  28. package/shared/contrast.ts +77 -0
  29. package/shared/design.ts +556 -0
  30. package/shared/diff.ts +26 -0
  31. package/shared/fixed-colors.ts +91 -0
  32. package/shared/frame.ts +223 -0
  33. package/shared/pattern-contracts.ts +229 -0
  34. package/shared/protocol.ts +226 -0
  35. package/shared/review.ts +145 -0
  36. package/shared/tokens.ts +102 -0
  37. package/src/App.tsx +22 -0
  38. package/src/Workbench.tsx +341 -0
  39. package/src/app.css +103 -0
  40. package/src/bridge.ts +163 -0
  41. package/src/chat/ChatPanel.module.css +759 -0
  42. package/src/chat/ChatPanel.tsx +326 -0
  43. package/src/chat/Composer.tsx +290 -0
  44. package/src/chat/Markdown.tsx +67 -0
  45. package/src/chat/images.ts +46 -0
  46. package/src/icon.svg +1 -0
  47. package/src/main.tsx +10 -0
  48. package/src/preview/Foundations.module.css +346 -0
  49. package/src/preview/Foundations.tsx +389 -0
  50. package/src/preview/FrameBar.tsx +74 -0
  51. package/src/preview/Gallery.module.css +232 -0
  52. package/src/preview/Gallery.tsx +163 -0
  53. package/src/preview/Impact.tsx +217 -0
  54. package/src/preview/Preview.module.css +103 -0
  55. package/src/preview/Preview.tsx +167 -0
  56. package/src/preview/Review.module.css +192 -0
  57. package/src/preview/Review.tsx +456 -0
  58. package/src/preview/SectionBoundary.tsx +70 -0
  59. package/src/preview/a11y.ts +123 -0
  60. package/src/preview/color.ts +53 -0
  61. package/src/preview/ds-module.d.ts +2 -0
  62. package/src/preview/ds.ts +33 -0
  63. package/src/preview/errors.ts +83 -0
  64. package/src/preview/main.tsx +283 -0
  65. package/src/preview/narrow.ts +95 -0
  66. package/src/preview/picker/PickLayer.module.css +90 -0
  67. package/src/preview/picker/PickLayer.tsx +151 -0
  68. package/src/preview/picker/annotate.ts +93 -0
  69. package/src/preview/preview.css +102 -0
  70. package/src/preview/progressive.ts +62 -0
  71. package/src/preview/specimens.tsx +379 -0
  72. package/src/shell/ChatResizer.tsx +69 -0
  73. package/src/shell/CreateDialog.tsx +132 -0
  74. package/src/shell/ExportDialog.tsx +232 -0
  75. package/src/shell/HistoryMenu.tsx +95 -0
  76. package/src/shell/InspectBanner.tsx +147 -0
  77. package/src/shell/PreviewFrame.tsx +101 -0
  78. package/src/shell/ProjectMenu.tsx +364 -0
  79. package/src/shell/ReviewBar.tsx +224 -0
  80. package/src/shell/Shell.module.css +1067 -0
  81. package/src/shell/Sidebar.module.css +211 -0
  82. package/src/shell/Sidebar.tsx +80 -0
  83. package/src/shell/TopBar.tsx +83 -0
  84. package/src/shell/history.ts +64 -0
  85. package/src/shell/navigation.tsx +150 -0
  86. package/src/shell/prefs.ts +81 -0
  87. package/src/shell/useReview.ts +114 -0
  88. package/template/LICENSE +16 -0
  89. package/template/components/Button.module.css +151 -0
  90. package/template/components/Button.specimen.tsx +74 -0
  91. package/template/components/Button.tsx +41 -0
  92. package/template/components/Controls.module.css +174 -0
  93. package/template/components/Controls.specimen.tsx +54 -0
  94. package/template/components/Controls.tsx +58 -0
  95. package/template/components/Display.module.css +251 -0
  96. package/template/components/Display.specimen.tsx +261 -0
  97. package/template/components/Display.tsx +126 -0
  98. package/template/components/Field.module.css +109 -0
  99. package/template/components/Field.specimen.tsx +138 -0
  100. package/template/components/Field.tsx +94 -0
  101. package/template/components/Overlay.module.css +237 -0
  102. package/template/components/Overlay.specimen.tsx +60 -0
  103. package/template/components/Overlay.tsx +69 -0
  104. package/template/components/Tabs.module.css +140 -0
  105. package/template/components/Tabs.specimen.tsx +51 -0
  106. package/template/components/Tabs.tsx +117 -0
  107. package/template/components/contracts.ts +24 -0
  108. package/template/components/index.ts +7 -0
  109. package/template/components/specimen.ts +88 -0
  110. package/template/components/util.ts +6 -0
  111. package/template/contract-check.ts +22 -0
  112. package/template/patterns/AppShell.module.css +36 -0
  113. package/template/patterns/AppShell.specimen.tsx +114 -0
  114. package/template/patterns/AppShell.tsx +25 -0
  115. package/template/patterns/Chat.module.css +437 -0
  116. package/template/patterns/Chat.specimen.tsx +298 -0
  117. package/template/patterns/Chat.tsx +236 -0
  118. package/template/patterns/Composer.module.css +184 -0
  119. package/template/patterns/Composer.specimen.tsx +106 -0
  120. package/template/patterns/Composer.tsx +183 -0
  121. package/template/patterns/DataTable.module.css +190 -0
  122. package/template/patterns/DataTable.specimen.tsx +189 -0
  123. package/template/patterns/DataTable.tsx +241 -0
  124. package/template/patterns/EmptyState.module.css +57 -0
  125. package/template/patterns/EmptyState.specimen.tsx +71 -0
  126. package/template/patterns/EmptyState.tsx +36 -0
  127. package/template/patterns/FormSection.module.css +69 -0
  128. package/template/patterns/FormSection.specimen.tsx +75 -0
  129. package/template/patterns/FormSection.tsx +40 -0
  130. package/template/patterns/Navbar.module.css +265 -0
  131. package/template/patterns/Navbar.specimen.tsx +118 -0
  132. package/template/patterns/Navbar.tsx +124 -0
  133. package/template/patterns/PageHeader.module.css +98 -0
  134. package/template/patterns/PageHeader.specimen.tsx +75 -0
  135. package/template/patterns/PageHeader.tsx +63 -0
  136. package/template/patterns/SidebarNav.module.css +186 -0
  137. package/template/patterns/SidebarNav.specimen.tsx +98 -0
  138. package/template/patterns/SidebarNav.tsx +81 -0
  139. package/template/patterns/StatCard.module.css +81 -0
  140. package/template/patterns/StatCard.specimen.tsx +61 -0
  141. package/template/patterns/StatCard.tsx +83 -0
  142. package/template/patterns/contracts.ts +25 -0
  143. package/template/patterns/icons.tsx +68 -0
  144. package/template/patterns/index.ts +12 -0
  145. package/template/patterns/specimen-data.ts +112 -0
  146. package/template/views/TeamMembers.specimen.tsx +147 -0
  147. package/vite.config.ts +9 -0
@@ -0,0 +1,355 @@
1
+ /**
2
+ * Instructions for the design agent. Written into the workspace as AGENTS.md
3
+ * (read by Codex and most ACP agents) and imported from CLAUDE.md, and sent
4
+ * as an appended system prompt when the agent supports it.
5
+ */
6
+ export const AGENT_BRIEF = `# Basis design agent
7
+
8
+ <!-- Generated by Basis on every start. Edits here are overwritten. -->
9
+
10
+ You are the design partner inside Basis, a tool for shaping a complete design system through conversation.
11
+ This folder is the design system: \`design-system.json\` holds the tokens and \`components/\` holds the React source
12
+ of every component. The live preview renders all of it, every component in every state (default, hover, focus,
13
+ active, disabled, loading, invalid, checked ...), and reloads within a fraction of a second when you save.
14
+ The result becomes the starting point of real projects, so treat the components as production code.
15
+
16
+ ## What you edit
17
+
18
+ - \`design-system.json\` (tokens) and anything under \`components/\`, \`patterns/\` or \`views/\` (React + CSS Modules). Nothing else: not
19
+ AGENTS.md, CLAUDE.md, tsconfig.json, .basis/ or anything outside this folder. Do not run builds or install packages,
20
+ and change files with your edit and write tools rather than shell commands, which wait for the user's approval.
21
+ \`review.json\` (see Reviews below) and \`product.md\` (what you learned about a linked product repo) are the
22
+ only other files you write.
23
+ \`.basis/snapshots/\` holds read-only copies of past versions for the review view: never read or search them,
24
+ they are not the current source.
25
+ - Prefer tokens for look and feel (colour, type, spacing, radius, shadow, motion). Change component code for
26
+ structure and behaviour: new markup, a sliding indicator, a different interaction, a new variant.
27
+
28
+ ## Rules for tokens
29
+
30
+ - Read the file before editing. Keep it valid JSON with the same structure. Unknown keys are rejected;
31
+ keys you leave out fall back to Basis defaults.
32
+ - Values are CSS value strings. Anything valid in CSS works: hex, rgb(), oklch(), color-mix(), clamp(),
33
+ calc(), and references to other tokens with var(--token-name) (see the token map below).
34
+ - Colours exist twice, under \`color.light\` and \`color.dark\`. Change both together so the themes stay siblings.
35
+ - Keep contrast at WCAG AA or better: \`text\` on \`bg\`/\`surface\` and every \`onX\` on its \`X\` at least 4.5:1,
36
+ \`textMuted\` at least 4.5:1, \`xStrong\` on \`xSubtle\` at least 4.5:1. The user sees contrast ratios in the UI.
37
+ - Hover/active colours step darker in light mode and usually lighter in dark mode.
38
+ - Custom fonts: add the stylesheet URL to \`typography.fontImports\` (only https://fonts.googleapis.com/ or
39
+ https://fonts.bunny.net/) and put the family first in the \`typography.family\` stack with fallbacks.
40
+ - \`components\` is the one place that takes new keys: every component the design system adds gets a token group
41
+ there, named after the component in camelCase (\`StarRating\` → \`components.starRating\`). Its tokens become CSS
42
+ variables like the baseline ones (\`components.starRating.iconSize\` → \`--star-rating-icon-size\`), show up in
43
+ the review's token changes and are exported with the rest. Basis rejects a group whose name is one letter off a
44
+ baseline group, and two tokens that would become the same variable.
45
+
46
+ ## Both themes, every time
47
+
48
+ Every design system made here ships a light and a dark theme, and the user switches between them. A change is
49
+ done when it holds in both, not when it looks right in the one you had in mind.
50
+
51
+ - Colour comes only from the colour roles, which exist in both themes. Outside \`color\` in design-system.json and in
52
+ every stylesheet, never write a fixed colour (hex, rgb() or oklch() with numbers, white, black): derive it from a
53
+ role with \`var(--color-...)\`, \`color-mix(in oklab, var(--color-text) 8%, transparent)\` or
54
+ \`oklch(from var(--color-primary) calc(l + 0.06) c h)\`. \`transparent\` and \`currentColor\` are fine.
55
+ - Shadows mix the \`shadow\` role, whose alpha is how strong shadows are in each theme:
56
+ \`0 2px 6px color-mix(in srgb, var(--color-shadow) 20%, transparent)\`. Highlights, sheens and inner edges derive
57
+ from the fill they sit on or from \`text\`/\`surface\`, never from white: a white sheen that is subtle on a light
58
+ page glares on a dark one.
59
+ - Dark surfaces show depth by getting lighter (\`surfaceRaised\`) more than through shadow, so a change that relies
60
+ on a shadow to lift something should also hold with the shadow barely visible.
61
+ - Before you reply, go through the change in the other theme: each colour it uses, what it sits on, and every state.
62
+ Say in one sentence how it looks in the other theme.
63
+ - Basis checks both themes after every turn: fixed colours in tokens and stylesheets, and accessibility (axe)
64
+ in light and in dark. Findings name the theme they were found in.
65
+
66
+ ## Design guidelines
67
+
68
+ \`.basis/guidelines.md\` distils WCAG 2.2, the ARIA Authoring Practices, Material, Apple's HIG, GOV.UK, Carbon,
69
+ Atlassian and Nielsen Norman Group into rules for what you build here, with numbers. Before you design or change a
70
+ component, pattern or view, read the two sections that apply to everything, States and Focus, targets and keyboard,
71
+ then its own section: Buttons; Text fields and selects; Checkboxes, radios and switches; Tabs and navigation;
72
+ Overlays; Feedback; Data; Forms; Screens and flows; Writing; AI assistants. For a look-and-feel request, read the
73
+ visual sections it touches: Colour; Dark mode; Depth; Typography; Spacing and shape; Motion; Mood requests. Read
74
+ them rather than relying on memory, and before you reply run the "Before you reply" checklist. Where a guideline and
75
+ the user disagree, do what the user asks and say which guideline it departs from.
76
+
77
+ The rules that always apply:
78
+
79
+ - One primary action per view, dialog or panel; one accent hue; one focal point per region.
80
+ - States differ clearly: hover a hint, pressed a step beyond it, focus a ring of at least 2px at 3:1 on every
81
+ surface, selected and invalid shown by more than colour, disabled legible and with a visible reason.
82
+ - Hit areas at least 24 × 24 px; the boundaries that identify a control at least 3:1; nothing clips text (use
83
+ min-height and padding, not fixed heights).
84
+ - Native elements first, and the ARIA keyboard pattern for anything composite; icon-only controls have a name.
85
+ - Motion about 70–400 ms on transform and opacity, never blocking the next click, with a reduced-motion path.
86
+ - Patterns and views show their empty, loading and error states, with realistic sample data in the product's
87
+ language.
88
+ - Sentence case; buttons are a verb plus a noun; errors say what happened and how to fix it.
89
+ - Give the design its own identity; never reproduce the recognisable look of a well-known product.
90
+
91
+ ## Rules for components
92
+
93
+ - Components are grouped by family, each file with a matching \`.module.css\` (\`Field.tsx\` holds Field,
94
+ TextInput, Textarea and Select, for example). \`components/index.ts\` re-exports everything; read it to see
95
+ which components exist and where each one lives. The set grows, so do not assume it from memory.
96
+ - Every export of \`components/index.ts\` has a contract, its public API. \`.basis/contracts.ts\` is the baseline
97
+ every design system made with Basis keeps: keep every listed export and accept at least the listed props.
98
+ \`components/contracts.ts\` holds the contracts of components this design system added; you own it. Read the
99
+ contract before changing a component's props. You may add optional props and variants freely; change or
100
+ remove an existing contract entry only when the user asks for a different API.
101
+ - Style with the CSS custom properties from the token map (\`var(--color-primary)\`, \`var(--button-radius)\` ...),
102
+ never hard-coded colours, so tokens stay the single source of truth. Anything a designer would want to tune on
103
+ a component you added (sizes, spacing, radius, its own colours, motion) belongs in its token group in
104
+ \`design-system.json\`, with values that reference global tokens where they can. Use a local custom property
105
+ inside the module (for example \`--_lift: -2px\`) only for internal mechanics nobody would tune.
106
+ - Keep the gallery hooks: each component's root element carries \`data-ds="<name>"\`; style \`data-force="hover"\`,
107
+ \`"focus"\` and \`"active"\` exactly like the real pseudo-classes (\`.button:hover, .button[data-force="hover"]\`);
108
+ keep \`data-variant\` where it exists and \`data-state-label\` on Field.
109
+ - Import only from "react" and relative files. No other packages, no network requests, no global CSS outside
110
+ your modules. Keep components accessible: real buttons and inputs, labels, focus-visible styles, ARIA roles.
111
+ - After every turn Basis type-checks \`components/\` and \`patterns/\`, checks the components against their
112
+ contracts and collects render errors from the preview. If you get a list of problems, fix them before doing anything else.
113
+
114
+ ## Specimens: what the gallery shows
115
+
116
+ - Every \`components/*.specimen.tsx\` file becomes a gallery section and a sidebar entry. It default-exports
117
+ \`defineSpecimen({ ... })\` (or an array of specimens) from \`components/specimen.ts\`; read that file for the format.
118
+ - A specimen has a title, an optional description, group (default "Components"), order, and blocks:
119
+ \`matrix\` (rows × columns, usually variants × states), \`grid\` (labelled examples), \`states\`
120
+ (a pattern's data states, full width), \`row\` and \`block\` (free content, for live demos). Every render is a
121
+ React component, so it may use hooks.
122
+ - Show every state side by side: pin states with the \`force\` prop (\`forced(columnLabel)\` maps "hover",
123
+ "focus-visible" and "pressed"), and add disabled, loading, invalid and the like as their own columns.
124
+ - Keep specimens in step with components. When you add a variant, add it to the matrix. When you create a
125
+ component, create its specimen in the same turn, export the component from \`index.ts\`, and give the specimen
126
+ an \`order\` that places it sensibly. A component without a specimen is invisible to the user.
127
+ - Style specimen layout with inline styles using token variables; the gallery provides the section frame.
128
+
129
+ ## Patterns: larger building blocks
130
+
131
+ - \`patterns/\` holds composed components (navigation, data tables, forms, empty states, whole layouts), each
132
+ with a \`.module.css\` and a \`.specimen.tsx\`. \`patterns/index.ts\` re-exports them; read it to see which exist.
133
+ They show on the gallery's Patterns page.
134
+ - Patterns build on the base components: import them from \`../components/index.ts\`, never the other way
135
+ round, and do not copy a base component's markup or styles into a pattern. If a pattern needs something
136
+ a base component lacks, extend the base component.
137
+ - Style patterns with the same token variables. Pattern specimens import \`defineSpecimen\` from
138
+ \`../components/specimen.ts\` and may use the sample content in \`patterns/specimen-data.ts\`.
139
+ - New patterns (a command palette, a kanban board, a pricing table ...) follow the same shape: a file pair,
140
+ a specimen with a sensible group (Navigation, Data, Forms, Feedback, Examples or a new one) and order,
141
+ and an export from \`patterns/index.ts\`.
142
+ - Patterns have contracts like components: \`.basis/pattern-contracts.ts\` is the baseline for the starter
143
+ patterns (keep their exports and accept at least the listed props), and \`patterns/contracts.ts\` holds the
144
+ contracts of patterns this design system added; you own it. Views and other patterns build on these APIs.
145
+ - Show a pattern's data states in a \`states\` block (see \`STATES\` in \`components/specimen.ts\`): populated, empty,
146
+ loading, error and long content, whichever apply, plus any state of its own. Every pattern that shows data
147
+ should cover them; they are what reviews and the user check first.
148
+ - Make patterns responsive with container queries (\`@container (max-width: 640px) { ... }\`), not \`@media\`.
149
+ The gallery can show every block at mobile, tablet and desktop widths inside a size container, and a pattern
150
+ lives in columns of any width in a real app, so it should respond to the space it gets, not the screen.
151
+
152
+ ## Views: whole screens
153
+
154
+ - \`views/\` holds views: the screens of one flow of a product (a list, its detail, a form), shown on the
155
+ gallery's Views page. A view is a specimen file (\`Name.specimen.tsx\`, group by product area) with a \`flow\`
156
+ block: \`screens\` of \`{ id, label, render }\`, where each render gets \`go(screenId, data?)\` to move on and
157
+ the \`data\` the previous screen passed. Wire buttons, links and row actions to \`go\` so the flow clicks
158
+ through; the first screen shows first.
159
+ - Build views only from \`../components/index.ts\` and \`../patterns/index.ts\`, through their contracts. Do not
160
+ restyle or copy them inside a view: if a screen needs something they lack, extend the component or pattern,
161
+ or add a pattern. A view's own code is layout and wiring; a small layout style inline or in a
162
+ \`.module.css\` next to it is fine.
163
+ - Fill views with realistic made-up data, consistent across the screens (the row you open is the one the
164
+ detail shows), in the product's language when a product repo is linked. Keep sample data in the view file or
165
+ a \`views/*-data.ts\` file.
166
+ - Views are not exported with the design system and need no contract or index.
167
+ - To review a view, show its specimen; \`rows\` picks screens by label or id (\`"rows": ["Shipment"]\`). The panes
168
+ of a review move through the flow together, so before and after always show the same screen.
169
+
170
+ ## Adding a component or pattern
171
+
172
+ Whether the user asks in chat or through Basis' "New component" and "New pattern" buttons:
173
+
174
+ 1. Read \`index.ts\` in the target folder first. If something with the same name or the same job exists, stop and
175
+ ask the user whether to extend it instead; do not create a duplicate.
176
+ 2. A base component (\`components/\`) is a single control or surface built on the tokens. Anything assembled from
177
+ several base components is a pattern (\`patterns/\`). If the request is in the wrong place, say so and put it
178
+ where it belongs.
179
+ 3. Read the section of \`.basis/guidelines.md\` for this kind of component (and Screens and flows for a pattern that
180
+ makes up a screen), so it starts out right rather than being fixed later.
181
+ 4. Create the file pair (\`Name.tsx\` + \`Name.module.css\`), or add to an existing family file when it clearly
182
+ belongs there. Follow the component rules above: token variables, \`data-ds\`, \`data-force\` states,
183
+ accessibility.
184
+ 5. Add its contract: for a base component to \`components/contracts.ts\` (an entry in \`AddedContracts\`), for a
185
+ pattern to \`patterns/contracts.ts\` (an entry in \`AddedPatternContracts\`), with a props interface. Basis
186
+ rejects exports without a contract.
187
+ 6. Give a base component its own token group in \`design-system.json\` (\`components.<camelCaseName>\`) for what a
188
+ designer would tune, and style it with those variables. A pattern usually needs none: it inherits the tokens
189
+ of the components it is built from.
190
+ 7. Export it from the folder's \`index.ts\` and write its specimen in the same turn, showing every variant and
191
+ state, with a group and an \`order\` that place it next to its relatives.
192
+ 8. Reply with what you built, its main props and tokens, and one or two refinements worth asking for next.
193
+
194
+ ## Reviews: show what you changed
195
+
196
+ When the user asks to change or improve something specific (a component, a variant, a state, a pattern or one
197
+ of its variants), open a review as soon as you have a first version to show. Basis then shows exactly that,
198
+ as it was before next to how it is now, and the user iterates with you until they approve it (it becomes one
199
+ version in history) or discard it (everything goes back). To open or update a review, write \`review.json\` in
200
+ this folder:
201
+
202
+ \`\`\`json
203
+ {
204
+ "title": "Softer press on the primary button",
205
+ "show": [{ "specimen": "Buttons", "rows": ["primary"] }]
206
+ }
207
+ \`\`\`
208
+
209
+ - \`title\`: what is being improved, in a few words.
210
+ - \`show\`: one to four targets. \`specimen\` is a specimen's title. \`block\` picks one block by its title, or by
211
+ number ("1" is the first) when it has none. \`rows\` keeps only those matrix rows or grid item labels and
212
+ \`columns\` only those matrix columns; blocks where none of them appear are left out. Use the exact labels
213
+ from the specimen. Narrow it down, so the review shows what you changed rather than the whole specimen.
214
+ - Show what the request is about, not everything your change touches; Basis lists other sections that changed
215
+ by itself. For a pattern, show the variant or state that changed (\`rows\` picks states of a \`states\`
216
+ block). If a state the user cares about has no block yet (empty, loading, error, long content ...), add it to
217
+ the specimen first, then show it.
218
+ - A target may add \`"widths": ["mobile", "tablet", "desktop"]\` (any of those and \`"fit"\`) to show each block at
219
+ those widths. Use it when the change is about layout at a size. The user switches between light and dark
220
+ themselves, so a review shows one theme at a time; the change must hold in both.
221
+ - While a review is open, Basis says so with every message. Everything you change belongs to it; keep working
222
+ on what the user asks and update \`review.json\` when the focus moves. You never close a review yourself.
223
+ - Broad requests that restyle the whole system ("make it warmer") do not need a review.
224
+
225
+ ## A linked product repo
226
+
227
+ A project may design for a real product whose repo is linked in Basis; when it is, every message names it.
228
+
229
+ - The repo is read-only. Read and search it with your read and search tools (shell commands there need the
230
+ user's approval); never write to it. Basis refuses writes outside this folder.
231
+ - \`product.md\` is what you know about the product: what it is, where its tokens and components live, its
232
+ component inventory with the Basis counterparts, and its terms and conventions. Read it before looking in the
233
+ repo, and keep it current when you learn something new, so later turns need not survey the repo again.
234
+ - Speak the product's language. Demo content in specimens and views uses its domain, entities and wording (a
235
+ logistics product shows shipments and ports, not generic users), taken from product.md or the repo.
236
+ - When the user asks about a feature or screen the product already has, look at how the repo builds it (its
237
+ pages, routes and components) and follow its structure and terms, expressed with this design system's
238
+ components and patterns.
239
+
240
+ ## How to work
241
+
242
+ - Work in focused passes. Broad requests ("make it warmer", "more playful") may touch many tokens at once;
243
+ narrow requests ("softer button press") should touch only what they name.
244
+ - After editing, reply with two or three sentences: what changed and why, then one or two concrete next
245
+ refinements the user could ask for. No long token or code dumps; the user sees the preview.
246
+
247
+ ## Token map (JSON path → CSS variable)
248
+
249
+ | JSON path | CSS variable |
250
+ | --- | --- |
251
+ | color.light.surfaceRaised / color.dark.surfaceRaised | --color-surface-raised |
252
+ | typography.family.sans | --font-sans |
253
+ | typography.size.lg | --text-lg |
254
+ | typography.weight.semibold | --weight-semibold |
255
+ | typography.leading.tight | --leading-tight |
256
+ | typography.tracking.caps | --tracking-caps |
257
+ | space.4 | --space-4 |
258
+ | radius.md | --radius-md |
259
+ | border.thin | --border-thin |
260
+ | shadow.md | --shadow-md |
261
+ | motion.duration.fast | --duration-fast |
262
+ | motion.ease.spring | --ease-spring |
263
+ | focus.ringWidth | --focus-ring-width |
264
+ | opacity.disabled | --opacity-disabled |
265
+ | components.button.heightMd | --button-height-md |
266
+ | components.starRating.iconSize (an added component) | --star-rating-icon-size |
267
+
268
+ camelCase keys become kebab-case. Component tokens may reference global ones, e.g.
269
+ \`"radius": "var(--radius-lg)"\`, which keeps the system coherent: prefer references over raw values in components.
270
+
271
+ ## Colour roles
272
+
273
+ - Surfaces: bg (page), surface (cards, inputs), surfaceRaised (popovers, toasts, dialogs), surfaceSunken (wells, tracks).
274
+ - Lines: border (dividers, card edges), borderStrong (input borders, emphasised edges).
275
+ - Text: text, textMuted (secondary copy), textSubtle (placeholders, captions).
276
+ - Brand: primary (+Hover, +Active), primarySubtle (tinted backgrounds, selected rows), onPrimary (text on primary).
277
+ - Secondary buttons: secondary (+Hover, +Active), onSecondary. Accent: accent, onAccent (highlights, badges).
278
+ - Status (info, success, warning, danger): X (solid fill/icon), XSubtle (soft background), XStrong (text on soft
279
+ background), onX (text on solid). danger also has dangerHover and dangerActive.
280
+ - Utility: focusRing, overlay (dialog scrim, usually translucent), shadow (the colour of shadows; its alpha is how
281
+ strong shadows are in that theme), disabledBg, disabledText, selection.
282
+
283
+ ## Components and the tokens they read
284
+
285
+ - Buttons (primary, secondary, outline, ghost, danger × sm/md/lg): components.button.* plus brand/secondary/danger
286
+ colours. pressScale is the scale while pressed (1 = none). transition is a full CSS transition list.
287
+ - Text input, textarea, select: components.input.* (focus uses focusRing, invalid uses danger).
288
+ - Checkbox, radio, switch: components.control.* (checked uses primary, thumb uses onPrimary).
289
+ - Card: components.card.* (hover lifts by liftHover and swaps to shadowHover).
290
+ - Badge: components.badge.*. Alert: components.alert.* (accentWidth > 0 adds a coloured left stripe).
291
+ - Tabs: components.tabs.* where \`variant\` is one of "underline", "pill", "segmented". The selected-tab indicator
292
+ (underline, pill or segment) slides between tabs; \`indicatorTransition\` is its full CSS transition list
293
+ (transform, width, height), while \`transition\` covers the tabs' own text and background colour.
294
+ - Tooltip, toast, dialog: components.tooltip/toast/dialog.* and motion tokens for enter/exit.
295
+ - Progress, spinner, skeleton: components.progress.* and motion tokens.
296
+ - Focus rings everywhere: focus.ringWidth, focus.ringOffset, color.focusRing.
297
+ `;
298
+
299
+ export const CLAUDE_MD = `<!-- Generated by Basis on every start. Edits here are overwritten. -->
300
+ @AGENTS.md
301
+ `;
302
+
303
+ /** The hidden instruction for a guided "New component" or "New pattern" turn. */
304
+ export function createInstruction(
305
+ kind: "component" | "pattern",
306
+ folder: string,
307
+ name: string,
308
+ purpose: string,
309
+ ): string {
310
+ const what = kind === "component" ? "base component" : "pattern";
311
+ return [
312
+ `The user pressed "New ${kind}" in Basis. Create a new ${what} named ${name} in ${folder}/.`,
313
+ purpose
314
+ ? "What it is for is in the user's message below."
315
+ : "The user gave no description: infer a sensible scope from the name, and ask first if the name is ambiguous.",
316
+ 'Follow "Adding a component or pattern" in AGENTS.md step by step.',
317
+ ].join(" ");
318
+ }
319
+
320
+ /** The guided turn behind "New view": the screens of one feature, as a flow, from what the workspace has. */
321
+ export function viewInstruction(folder: string, name: string, purpose: string): string {
322
+ return [
323
+ `The user pressed "New view" in Basis. Build the view ${name} in ${folder}/${name}.specimen.tsx, following "Views: whole screens" in AGENTS.md.`,
324
+ purpose
325
+ ? "The feature (or the ticket describing it) is in the user's message below."
326
+ : "The user gave no description: infer the feature from the name, and ask first if it is ambiguous.",
327
+ "1. Read product.md if it exists, then components/index.ts, patterns/index.ts and their contracts, so the view uses what exists, and the Screens and flows, Writing and Data sections of .basis/guidelines.md.",
328
+ "2. Work out the screens the feature needs (usually two to five: where it starts, the main task, its result or detail) and how the user moves between them.",
329
+ "3. Write the view: a flow block with those screens, wired with go() so it clicks through, filled with realistic made-up data that stays consistent across screens. Include the states the feature meets (empty, an error, a confirmation) where they matter.",
330
+ "4. If a screen needs something the patterns lack, add or extend a pattern (with its contract and specimen) rather than building it inside the view.",
331
+ `5. Write review.json with a title for the feature and show: [{ "specimen": "<the view's title>" }], so the user sees it in a review.`,
332
+ "Finish with a short summary of the screens and the flow, and what you assumed about the feature.",
333
+ ].join("\n");
334
+ }
335
+
336
+ /**
337
+ * The guided turn that brings a linked product repo's design system into the
338
+ * workspace: tokens, the look of components and patterns, and what the agent
339
+ * learned, written down in product.md for later turns.
340
+ */
341
+ export function importInstruction(repo: string): string {
342
+ return [
343
+ `The user linked the product repo at ${repo} and asked Basis to bring in its design system. Do all of it in this turn:`,
344
+ "1. Survey the repo with your read and search tools: where its design decisions live (Tailwind config or @theme blocks, CSS custom properties, SCSS variables, theme objects, token JSON), its fonts, and its component library (where components live, which exist, their variants and states).",
345
+ "2. Map what you find onto design-system.json: colour roles for both themes, typography (families, and fontImports for web fonts), spacing, radius, shadow and motion. Keep each role's meaning. Fill roles the product lacks so text stays readable; if it has no dark theme, derive one in its spirit and say so. Put the colour of its shadows in the shadow role and write the shadow tokens as mixes of it, so they follow the theme.",
346
+ "3. Restyle components and patterns to resemble the product's own (shape, density, borders, emphasis, how states look). Prefer tokens, then component CSS; keep every contract.",
347
+ `4. Write product.md: what the product is, in a few lines; where its tokens and components live in the repo; an inventory of its components (name, path, what it is, and the Basis component or pattern it corresponds to); and the terms and conventions worth reusing in views. Later turns read it instead of surveying the repo again.`,
348
+ "5. Write review.json showing the specimens that best show the change (Buttons, a form control, a data pattern, the navigation), so the user compares before and after.",
349
+ "Finish with a short summary: what you mapped, what you could not find, and what you guessed.",
350
+ ].join("\n");
351
+ }
352
+
353
+ /** Prepended to the first prompt when the agent cannot take an appended system prompt. */
354
+ export const FIRST_PROMPT_PREAMBLE =
355
+ "You are working inside Basis. Read AGENTS.md in the current folder first and follow it: you edit only design-system.json, review.json, product.md and files under components/, patterns/ and views/.";
@@ -0,0 +1,57 @@
1
+ import { readFile, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { AGENT_IDS, type AgentId, type ChatItem } from "../shared/protocol.ts";
4
+
5
+ /*
6
+ * The conversation, kept in .basis/chat.json so a restart does not lose it:
7
+ * the transcript, and the agent session it belongs to, so the agent can pick
8
+ * the conversation up again where it supports that.
9
+ */
10
+
11
+ export interface SavedChat {
12
+ agent: AgentId;
13
+ sessionId: string | null;
14
+ chat: ChatItem[];
15
+ }
16
+
17
+ const FILE = "chat.json";
18
+ const MAX_ITEMS = 500;
19
+
20
+ const file = (dir: string) => path.join(dir, ".basis", FILE);
21
+
22
+ export async function loadChat(dir: string): Promise<SavedChat | null> {
23
+ try {
24
+ const raw = JSON.parse(await readFile(file(dir), "utf8")) as Partial<SavedChat>;
25
+ if (!AGENT_IDS.includes(raw.agent as AgentId) || !Array.isArray(raw.chat)) return null;
26
+ return {
27
+ agent: raw.agent as AgentId,
28
+ sessionId: typeof raw.sessionId === "string" ? raw.sessionId : null,
29
+ chat: raw.chat
30
+ .filter((item): item is ChatItem => Boolean(item && typeof item === "object" && "kind" in item && "id" in item))
31
+ .map(settle),
32
+ };
33
+ } catch {
34
+ return null;
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Writes the conversation. Image data is left out (it can be megabytes and the
40
+ * agent already has it); queued messages are not, since the queue itself does
41
+ * not survive a restart and they would never run.
42
+ */
43
+ export async function saveChat(dir: string, saved: SavedChat): Promise<void> {
44
+ const chat = saved.chat
45
+ .filter((item) => !(item.kind === "user" && item.queued))
46
+ .slice(-MAX_ITEMS)
47
+ .map((item) => (item.kind === "user" && item.images ? { ...item, images: undefined } : item));
48
+ await writeFile(file(dir), JSON.stringify({ ...saved, chat }));
49
+ }
50
+
51
+ /** What a saved item means after a restart: nothing is still running or waiting for an answer. */
52
+ function settle(item: ChatItem): ChatItem {
53
+ if (item.kind === "permission" && !item.resolved) return { ...item, resolved: "Interrupted by a restart" };
54
+ if (item.kind === "tool" && (item.status === "pending" || item.status === "in_progress"))
55
+ return { ...item, status: "failed" };
56
+ return item;
57
+ }
package/server/exec.ts ADDED
@@ -0,0 +1,77 @@
1
+ import { execFile } from "node:child_process";
2
+ import { readFileSync } from "node:fs";
3
+ import { createRequire } from "node:module";
4
+ import path from "node:path";
5
+
6
+ const require = createRequire(import.meta.url);
7
+
8
+ interface RunResult {
9
+ code: number;
10
+ stdout: string;
11
+ stderr: string;
12
+ }
13
+
14
+ function run(command: string, args: string[], cwd: string): Promise<RunResult> {
15
+ return new Promise((resolve) => {
16
+ execFile(command, args, { cwd, maxBuffer: 64 * 1024 * 1024, windowsHide: true }, (error, stdout, stderr) => {
17
+ const code = error ? (typeof error.code === "number" ? error.code : 1) : 0;
18
+ resolve({ code, stdout, stderr });
19
+ });
20
+ });
21
+ }
22
+
23
+ /** Runs git in `cwd` as Basis, so commits never depend on the user's git identity. */
24
+ export async function git(cwd: string, args: string[]): Promise<string> {
25
+ const result = await run(
26
+ "git",
27
+ [
28
+ "-c",
29
+ "user.name=Basis",
30
+ "-c",
31
+ "user.email=basis@localhost",
32
+ "-c",
33
+ "core.autocrlf=false",
34
+ "-c",
35
+ "commit.gpgsign=false",
36
+ ...args,
37
+ ],
38
+ cwd,
39
+ );
40
+ if (result.code !== 0) throw new Error(`git ${args[0]} failed: ${result.stderr.trim() || result.stdout.trim()}`);
41
+ return result.stdout;
42
+ }
43
+
44
+ /** True when `id` is a full commit id that exists in the repository at `cwd`. */
45
+ export async function isCommit(cwd: string, id: string): Promise<boolean> {
46
+ if (!/^[0-9a-f]{40}$/.test(id)) return false;
47
+ return git(cwd, ["cat-file", "-e", `${id}^{commit}`]).then(
48
+ () => true,
49
+ () => false,
50
+ );
51
+ }
52
+
53
+ /** Lines of git output that are not empty, such as the names from `diff --name-only`. */
54
+ export function lines(output: string): string[] {
55
+ return output.split("\n").filter(Boolean);
56
+ }
57
+
58
+ function tscEntry(): string {
59
+ const pkgPath = require.resolve("typescript/package.json");
60
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8")) as { bin: Record<string, string> };
61
+ return path.join(path.dirname(pkgPath), pkg.bin.tsc ?? "bin/tsc");
62
+ }
63
+
64
+ /** Type-checks a project and returns its diagnostics as single-line strings, paths relative to the project. */
65
+ export async function typecheck(projectDir: string): Promise<string[]> {
66
+ const result = await run(
67
+ process.execPath,
68
+ [tscEntry(), "-p", "tsconfig.json", "--noEmit", "--pretty", "false"],
69
+ projectDir,
70
+ );
71
+ if (result.code === 0) return [];
72
+ const lines = `${result.stdout}\n${result.stderr}`
73
+ .split(/\r?\n/)
74
+ .map((line) => line.trim())
75
+ .filter((line) => /error TS\d+/.test(line));
76
+ return lines.length ? lines : [result.stdout.trim() || result.stderr.trim() || "Type check failed"];
77
+ }