@eventmodelers/cli 1.0.54 → 1.0.55
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.
- package/README.md +6 -0
- package/cli.js +24 -0
- package/package.json +1 -1
- package/shared/build-kit/lib/ralph.js +103 -5
- package/shared/skills/learn-eventmodelers-api/SKILL.md +3 -0
- package/stacks/react/templates/.claude/skills/build-automation/SKILL.md +42 -0
- package/stacks/react/templates/.claude/skills/build-state-change/SKILL.md +43 -0
- package/stacks/react/templates/.claude/skills/build-state-view/SKILL.md +42 -0
- package/stacks/react/templates/build-kit/CLAUDE.md +62 -0
- package/stacks/react/templates/build-kit/README.md +79 -0
- package/stacks/react/templates/build-kit/lib/AGENT.md +47 -0
- package/stacks/react/templates/build-kit/lib/backend-prompt.md +135 -0
- package/stacks/react/templates/build-kit/lib/prompt.md +139 -0
- package/stacks/react/templates/build-kit/lib/ralph.js +508 -0
- package/stacks/react/templates/build-kit/package.json +9 -0
- package/stacks/react/templates/build-kit/ralph-claude.js +107 -0
- package/stacks/react/templates/build-kit/ralph-ollama.js +40 -0
- package/stacks/supabase-react/templates/.claude/skills/build-state-change/SKILL.md +305 -0
- package/stacks/supabase-react/templates/.claude/skills/build-state-view/SKILL.md +238 -0
- package/stacks/supabase-react/templates/.claude/skills/init-style-guide/SKILL.md +60 -0
- package/stacks/supabase-react/templates/.claude/skills/learn-styleguide/SKILL.md +28 -0
- package/stacks/supabase-react/templates/.claude/skills/learn-styleguide/references/README.md +5 -0
- package/stacks/supabase-react/templates/build-kit/CLAUDE.md +149 -0
- package/stacks/supabase-react/templates/build-kit/lib/AGENT.md +47 -0
- package/stacks/supabase-react/templates/build-kit/lib/backend-prompt.md +139 -0
- package/stacks/supabase-react/templates/build-kit/lib/prompt.md +145 -0
- package/stacks/supabase-react/templates/root/.env.example +12 -0
- package/stacks/supabase-react/templates/root/.oxlintrc.json +9 -0
- package/stacks/supabase-react/templates/root/README.md +49 -0
- package/stacks/supabase-react/templates/root/index.html +13 -0
- package/stacks/supabase-react/templates/root/package.json +26 -0
- package/stacks/supabase-react/templates/root/public/favicon.svg +1 -0
- package/stacks/supabase-react/templates/root/public/icons.svg +24 -0
- package/stacks/supabase-react/templates/root/src/App.css +184 -0
- package/stacks/supabase-react/templates/root/src/App.tsx +122 -0
- package/stacks/supabase-react/templates/root/src/assets/hero.png +0 -0
- package/stacks/supabase-react/templates/root/src/assets/react.svg +1 -0
- package/stacks/supabase-react/templates/root/src/assets/vite.svg +1 -0
- package/stacks/supabase-react/templates/root/src/index.css +111 -0
- package/stacks/supabase-react/templates/root/src/lib/api.ts +127 -0
- package/stacks/supabase-react/templates/root/src/lib/supabase.ts +10 -0
- package/stacks/supabase-react/templates/root/src/main.tsx +10 -0
- package/stacks/supabase-react/templates/root/src/slices/.gitkeep +0 -0
- package/stacks/supabase-react/templates/root/src/vite-env.d.ts +13 -0
- package/stacks/supabase-react/templates/root/tsconfig.app.json +26 -0
- package/stacks/supabase-react/templates/root/tsconfig.json +7 -0
- package/stacks/supabase-react/templates/root/tsconfig.node.json +23 -0
- package/stacks/supabase-react/templates/root/vite.config.ts +7 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: build-state-change
|
|
3
|
+
description: Build a write-side React slice for the Supabase stack — one dedicated component per COMMAND (splitting into a hook and/or a colocated Context/Provider only when the slice actually needs one) that POSTs to its slice-defined apiEndpoint via the shared api.ts, driven by the marked-up region of its actor HTML_SCREEN
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Build State Change Slice
|
|
7
|
+
|
|
8
|
+
> Before doing anything else, read the slice definition from
|
|
9
|
+
> `.build-kit/.slices/<contextSlug>/<sliceFolder>/slice.json`. This file (plus, when a screen is
|
|
10
|
+
> involved, the live screen node fetched in Step 2) is the **source of truth** for every field,
|
|
11
|
+
> event, and endpoint — never invent a field, prop, or path that isn't there.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## What a State Change Slice is
|
|
16
|
+
|
|
17
|
+
One `COMMAND` element (`slice.json.commands[]`) that, when submitted, causes one or more `EVENT`
|
|
18
|
+
elements (`slice.json.events[]`) to be recorded by the backend. This stack builds only the
|
|
19
|
+
**frontend half**: a dedicated React component that collects the command's data (via props — this
|
|
20
|
+
component never fetches or owns its own data) and POSTs it to an already-existing backend
|
|
21
|
+
endpoint, via `postCommand` in `src/lib/api.ts` — never `fetch` directly. It does not generate any
|
|
22
|
+
server-side code.
|
|
23
|
+
|
|
24
|
+
Every component must be renderable and testable **in isolation**, with no real backend running —
|
|
25
|
+
see Step 4 (numbered `samples/`) and `src/lib/api.ts`'s `VITE_DATA_MODE=mock`, which makes
|
|
26
|
+
`postCommand` resolve locally instead of hitting the network.
|
|
27
|
+
|
|
28
|
+
## Step 1 — Read slice.json
|
|
29
|
+
|
|
30
|
+
Extract:
|
|
31
|
+
- **title**, **context** — slice identity, used for file paths (see Step 3).
|
|
32
|
+
- **commands[0]** — the command element. Each `Element` carries:
|
|
33
|
+
- `title` — the command name, e.g. `"ReserveBike"`.
|
|
34
|
+
- `fields[]` — one entry per data field (`name`, `type`, `optional?`, `cardinality?`, `example?`,
|
|
35
|
+
`subfields?`). This is the **exact and complete** set of props the component takes — no more,
|
|
36
|
+
no fewer.
|
|
37
|
+
- `apiEndpoint` — the path to POST to. **Read this literally from slice.json when it's present**
|
|
38
|
+
— do not guess a REST convention, invent a path, or infer one from the command name while a real
|
|
39
|
+
one exists. **When `apiEndpoint` is missing** (no backend endpoint decided yet), flag it but keep
|
|
40
|
+
building — this is a partial variant of `request-feedback`, not a full stop:
|
|
41
|
+
1. Post a comment on this slice node naming the command and stating the endpoint is missing
|
|
42
|
+
(`mcp__eventmodelers__add_comment`, same call `request-feedback` Step 3 uses), and mark the
|
|
43
|
+
slice `Blocked` (`mcp__eventmodelers__update_slice_status`, same as `request-feedback` Step 4).
|
|
44
|
+
2. Unlike a normal `request-feedback` escalation, **do not stop here** — continue on to build the
|
|
45
|
+
component exactly as normal, but against a mocked backend: give `postCommand` a clearly
|
|
46
|
+
provisional path, e.g. `/api/mock/<command-title-kebab-case>`, with a one-line comment marking
|
|
47
|
+
it as a placeholder pending the real endpoint. `postCommand` already resolves locally under
|
|
48
|
+
`VITE_DATA_MODE=mock` regardless of what the path string is (see `src/lib/api.ts`), so the
|
|
49
|
+
component is fully functional offline either way — only make Step 4's samples do extra work in
|
|
50
|
+
this case: cover every `command.fields[]` entry with realistic, meaningful values (never
|
|
51
|
+
placeholders like `"foo"`/`"test"`), plus whatever edge cases `specifications[]` call out,
|
|
52
|
+
since those samples are the only data this slice will see until a real endpoint is wired in.
|
|
53
|
+
3. Leave the slice's status as `Blocked` when you finish (see this stack's `CLAUDE.md` "Building
|
|
54
|
+
a Slice" step 6) — the built, mocked component still needs a real endpoint before it's done,
|
|
55
|
+
so don't flip it to `Done`.
|
|
56
|
+
- `description` — implementation hints (validation rules, business constraints).
|
|
57
|
+
- **events[]** — informational only (what the backend is expected to emit); this stack does not
|
|
58
|
+
act on them directly.
|
|
59
|
+
- **screens[]** — the actor `SCREEN` element(s) that trigger this command, if any. Empty when the
|
|
60
|
+
command has no UI trigger (rare — build the component from fields alone in that case, skip Step
|
|
61
|
+
2).
|
|
62
|
+
- **specifications[]** — GWT scenarios; treat as acceptance criteria for the component's business
|
|
63
|
+
logic (e.g. "given empty `email`, submit is disabled").
|
|
64
|
+
|
|
65
|
+
> **Comments**: each element carries `comments: string[]` (board comments) — read them as
|
|
66
|
+
> implementation hints, and resolve consumed ones via
|
|
67
|
+
> `POST <BASE_URL>/api/org/<ORG_ID>/boards/<BOARD_ID>/nodes/<nodeId>/comments/<commentId>/resolve`.
|
|
68
|
+
|
|
69
|
+
## Step 2 — Read the screen, and especially its marks
|
|
70
|
+
|
|
71
|
+
`slice.json`'s `screens[]` entries only carry `id`/`title`/`fields[]`/`description` (fields already
|
|
72
|
+
scoped to this slice) — not the actual markup. Fetch the live node to get that:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
mcp__eventmodelers__get_node { "boardId": "<BOARD_ID>", "nodeId": "<screen.id>" }
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
This returns the full `meta`, including `pages: string[]` (one HTML fragment per page) and,
|
|
79
|
+
if the screen is shared across several slices, `marks: [{ id, color, pageIndex, blurOutside?,
|
|
80
|
+
whiteOutside? }]`.
|
|
81
|
+
|
|
82
|
+
- **If `meta.marks` is non-empty**: the same visual screen is shared by multiple slices, each
|
|
83
|
+
highlighting a different part. Find the mark(s) belonging to this node, then in
|
|
84
|
+
`meta.pages[mark.pageIndex]` locate the element carrying `data-em-mark-id="<mark.id>"` — **that
|
|
85
|
+
HTML subtree, not the rest of the page, is the blueprint for what this component builds.**
|
|
86
|
+
Ignore markup outside it; it belongs to other slices.
|
|
87
|
+
- **If `meta.marks` is empty**: the whole page (every entry in `meta.pages`, in order, for a
|
|
88
|
+
multi-step flow) is the blueprint.
|
|
89
|
+
- **If `screens[]` was empty in Step 1**: there is no visual reference — build the component from
|
|
90
|
+
`fields[]` alone, with plain, unstyled form markup.
|
|
91
|
+
|
|
92
|
+
If this project has a `learn-styleguide` skill installed (`.claude/skills/learn-styleguide/`),
|
|
93
|
+
read its references before turning markup into JSX and prefer its tokens/component classes over
|
|
94
|
+
whatever ad hoc styling the mockup used — the mockup gives you *structure and content*, the style
|
|
95
|
+
guide gives you the real visual language when the two disagree.
|
|
96
|
+
|
|
97
|
+
**Also resolve this screen's lane** (skip if `screens[]` was empty in Step 1 — see Step 3's file
|
|
98
|
+
path): the actor row this screen sits in becomes a folder segment. Fetch it cheaply with:
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
mcp__eventmodelers__get_board_outline { "boardId": "<BOARD_ID>", "chapterId": "<screen node's chapterId>" }
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
This returns `{id, type, title, lane}` for every node in the chapter — find the entry whose `id`
|
|
105
|
+
matches `screen.id` and take its `lane`. Slugify it the same way `contextSlug` is derived
|
|
106
|
+
(lowercase, spaces to hyphens, non-alphanumeric stripped) to get `laneSlug`.
|
|
107
|
+
|
|
108
|
+
## Step 3 — Build the dedicated component
|
|
109
|
+
|
|
110
|
+
**File**: `src/slices/<contextSlug>/<laneSlug>/<sliceFolder>/<CommandTitle>.tsx` — `contextSlug` and
|
|
111
|
+
`sliceFolder` are derived exactly as in `load-slice` (lowercase-slugified context; slice title
|
|
112
|
+
lowercased with spaces removed and any `slice:` prefix stripped); `laneSlug` comes from Step 2
|
|
113
|
+
above. Omit the `<laneSlug>` segment (fall back to `<contextSlug>/<sliceFolder>/`) only for the
|
|
114
|
+
rare headless command with no `screens[]` at all — there's no lane to resolve without a screen.
|
|
115
|
+
Note this nests one level deeper than the staging data in
|
|
116
|
+
`.build-kit/.slices/<contextSlug>/<sliceFolder>/slice.json` — `load-slice`'s own local index has no
|
|
117
|
+
lane concept, so the frontend path isn't a strict 1:1 mirror of it. `CommandTitle` is the command's
|
|
118
|
+
`title` verbatim (e.g. `ReserveBike.tsx`).
|
|
119
|
+
|
|
120
|
+
**Markup**: translate the blueprint HTML from Step 2 into JSX **1:1** — same tags, same classes
|
|
121
|
+
(Bulma or whatever the mockup used), same layout and copy. This is not a redesign; the screen *is*
|
|
122
|
+
the design. Only change what must become dynamic:
|
|
123
|
+
- A field's example value becomes that input's controlled `value`.
|
|
124
|
+
- An element repeated for a `cardinality: "List"` field becomes a `.map()` over that field.
|
|
125
|
+
- `class`/`style`/structure otherwise stay exactly as drawn.
|
|
126
|
+
|
|
127
|
+
**Props**: exactly `command.fields[]`, typed per each field's `type` (`String`→`string`,
|
|
128
|
+
`Boolean`→`boolean`, `Int`/`Long`→`number`, etc. — `subfields[]` become a nested object type),
|
|
129
|
+
respecting `optional`. Add two callback props: `onSuccess?: (result: TResponse) => void` and
|
|
130
|
+
`onError?: (error: ApiError) => void`. **No other inputs** — no context/store reads, no fetching
|
|
131
|
+
inside the component. All business data the component needs arrives via props from its parent.
|
|
132
|
+
|
|
133
|
+
**Default — keep the logic in the component.** For an ordinary command (one form, one submit
|
|
134
|
+
action, which is most of them) local state and the `postCommand` call live directly inside the
|
|
135
|
+
component. This is the common case and needs nothing more:
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
// ReserveBike.tsx
|
|
139
|
+
import { useState } from 'react';
|
|
140
|
+
import { postCommand, ApiError } from '../../../../lib/api'; // one less '../' if this command has no lane segment
|
|
141
|
+
|
|
142
|
+
export interface ReserveBikeProps {
|
|
143
|
+
bikeId: string;
|
|
144
|
+
customerId: string;
|
|
145
|
+
onSuccess?: (result: unknown) => void;
|
|
146
|
+
onError?: (error: ApiError) => void;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function ReserveBike({ bikeId, customerId, onSuccess, onError }: ReserveBikeProps) {
|
|
150
|
+
const [submitting, setSubmitting] = useState(false);
|
|
151
|
+
const [error, setError] = useState<string | null>(null);
|
|
152
|
+
|
|
153
|
+
const handleSubmit = async () => {
|
|
154
|
+
setSubmitting(true);
|
|
155
|
+
setError(null);
|
|
156
|
+
try {
|
|
157
|
+
const result = await postCommand('/api/reservebike', { bikeId, customerId });
|
|
158
|
+
onSuccess?.(result);
|
|
159
|
+
} catch (err) {
|
|
160
|
+
const message = err instanceof ApiError ? err.message : 'Something went wrong';
|
|
161
|
+
setError(message);
|
|
162
|
+
if (err instanceof ApiError) onError?.(err);
|
|
163
|
+
} finally {
|
|
164
|
+
setSubmitting(false);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// ...JSX translated from the marked screen fragment, wired to handleSubmit...
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The component owns only: local form/loading/error state, whatever client-side validation the
|
|
173
|
+
`specifications[]`/field `optional` flags imply, building the request body from props, and calling
|
|
174
|
+
`postCommand(command.apiEndpoint, body)`. It never imports `supabase`, builds headers, or calls
|
|
175
|
+
`fetch` directly — that is exclusively `src/lib/api.ts`'s job. In `VITE_DATA_MODE=mock`,
|
|
176
|
+
`postCommand` never hits the network at all — it resolves immediately, so this component is fully
|
|
177
|
+
exercisable (submitting/success/error UI states) offline.
|
|
178
|
+
|
|
179
|
+
**Extract a `use<CommandTitle>.ts` hook only when it earns its keep** — e.g. the exact same logic
|
|
180
|
+
is genuinely reused by more than one component in this slice, or the non-rendering logic (several
|
|
181
|
+
async steps, non-trivial multi-field validation) has grown large enough that pulling it out of the
|
|
182
|
+
component measurably improves readability. Don't extract a hook by default, for uniformity with
|
|
183
|
+
other slices, or "for consistency" — a small command's logic reads perfectly fine inline, and a
|
|
184
|
+
hook file that's just a thin wrapper around `postCommand` is pure ceremony working against
|
|
185
|
+
deletability, not for it (one more file to keep in sync instead of one). When it *is* warranted,
|
|
186
|
+
move the same logic verbatim into a colocated `use<CommandTitle>.ts` and have the component call
|
|
187
|
+
it — nothing about the logic itself changes, only where it lives.
|
|
188
|
+
|
|
189
|
+
## Step 4 — Add numbered samples
|
|
190
|
+
|
|
191
|
+
Every command component ships a `samples/` folder colocated next to it, mirroring
|
|
192
|
+
`build-state-view`'s convention:
|
|
193
|
+
`src/slices/<contextSlug>/<laneSlug>/<sliceFolder>/samples/sample-<N>.json`. Since this component
|
|
194
|
+
is driven entirely by props, each sample is a **full set of prop values** — there's no query for
|
|
195
|
+
`postCommand` to serve (it's already mock-aware on its own, see Step 3). A sample is for whatever
|
|
196
|
+
renders this component in isolation (a `src/pages/` composition, or ad hoc while developing) to
|
|
197
|
+
spread as props, e.g. `<ReserveBike {...sample1} onSuccess={...} />`.
|
|
198
|
+
|
|
199
|
+
Add `sample-1.json` for the ordinary case (`command.fields[].example` values); add more numbered
|
|
200
|
+
ones for other scenarios `specifications[]` calls out (an edge case, an `optional` field omitted,
|
|
201
|
+
...). As in `build-state-view`, the same number across different components' samples is meant to
|
|
202
|
+
represent one coherent scenario across the app, not an arbitrary per-component index.
|
|
203
|
+
|
|
204
|
+
## Step 5 — Context/Provider, only if the marked fragment needs shared local state
|
|
205
|
+
|
|
206
|
+
Skip this step for the overwhelming majority of commands — an ordinary single-form command is
|
|
207
|
+
fully covered by Step 3 alone, and adding a Context there is needless indirection with nothing to
|
|
208
|
+
show for it.
|
|
209
|
+
|
|
210
|
+
Reach for a Context **only** when the blueprint from Step 2 decomposes into several sibling
|
|
211
|
+
components that must coordinate (e.g. a multi-step wizard, a shared "selected row" between a list
|
|
212
|
+
and a submit bar) — i.e. state that a single component's own `useState` genuinely cannot hold
|
|
213
|
+
because more than one sibling needs to read or change it. Follow the same `Context`/`Provider`/hook
|
|
214
|
+
shape this project's components use elsewhere (see `useReadOnly`/`ReadOnlyProvider` and
|
|
215
|
+
`useUser`/`UserProvider` for the two variants):
|
|
216
|
+
|
|
217
|
+
```tsx
|
|
218
|
+
// ReserveBikeContext.tsx — colocated in the SAME slice folder, never in src/lib
|
|
219
|
+
import { createContext, useContext } from 'react';
|
|
220
|
+
|
|
221
|
+
interface ReserveBikeContextType {
|
|
222
|
+
selectedBikeId: string | null;
|
|
223
|
+
selectBike: (bikeId: string) => void;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const ReserveBikeContext = createContext<ReserveBikeContextType | null>(null);
|
|
227
|
+
|
|
228
|
+
export function ReserveBikeProvider({ children }: { children: React.ReactNode }) {
|
|
229
|
+
// ...state shared by this slice's sibling components...
|
|
230
|
+
return <ReserveBikeContext.Provider value={{ selectedBikeId, selectBike }}>{children}</ReserveBikeContext.Provider>;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function useReserveBikeContext() {
|
|
234
|
+
const ctx = useContext(ReserveBikeContext);
|
|
235
|
+
if (!ctx) throw new Error('useReserveBikeContext must be used within ReserveBikeProvider');
|
|
236
|
+
return ctx;
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Throw when the context is missing (as above) — there is no sensible default for slice-scoped state,
|
|
241
|
+
and failing fast surfaces a sub-component rendered outside its own slice's provider immediately.
|
|
242
|
+
A plain default value (no throw) is only appropriate for a genuinely app-wide, always-available
|
|
243
|
+
value — that belongs in `src/lib`, not here (see **Deletability** in `.build-kit/CLAUDE.md`).
|
|
244
|
+
|
|
245
|
+
**File**: `src/slices/<contextSlug>/<laneSlug>/<sliceFolder>/<CommandTitle>Context.tsx` — colocated
|
|
246
|
+
with the hook and component, never imported by another slice. The command's own `.tsx` file
|
|
247
|
+
renders the `Provider` at its root, wrapping the sibling sub-components that call the context hook.
|
|
248
|
+
|
|
249
|
+
## Step 6 — Compose onto the shared page, if this screen's name is shared
|
|
250
|
+
|
|
251
|
+
A screen's **title is the page name**. The `html-screen` skill gives each slice its own copy of a
|
|
252
|
+
shared visual screen — one node per slice, each with a different mark — so two slices whose
|
|
253
|
+
screens carry the **same title** are meant to sit together on **one real page**, arranged exactly
|
|
254
|
+
as their marks show. The number of *distinct* screen titles across the board is a direct hint at
|
|
255
|
+
how many pages this app needs — not one page per slice.
|
|
256
|
+
|
|
257
|
+
1. Check whether any other slice references a screen with the same `title` as this slice's screen.
|
|
258
|
+
Search every context's local index, not just this slice's own:
|
|
259
|
+
```bash
|
|
260
|
+
grep -l '"title": *"<ScreenTitle>"' .build-kit/.slices/*/index.json
|
|
261
|
+
```
|
|
262
|
+
(Run `/load-slice` with no filter first if the board's other slices haven't been pulled locally
|
|
263
|
+
yet — this check is only as complete as what's on disk.)
|
|
264
|
+
2. **No match** (this is the only slice on this screen so far): nothing further to do here — this
|
|
265
|
+
slice's component stands alone for now. A shared page gets created the first time a *second*
|
|
266
|
+
slice on the same screen is built.
|
|
267
|
+
3. **Match found**: this screen is a shared page. Its composition lives at
|
|
268
|
+
`src/pages/<ScreenTitle>.tsx` (PascalCase, spaces stripped) — the **one place** in this codebase
|
|
269
|
+
allowed to import across slice folders; a slice component itself never does (see **Deletability**
|
|
270
|
+
in `.build-kit/CLAUDE.md`).
|
|
271
|
+
- Fetch the **whole, unmarked** page markup for this screen (every entry in `meta.pages`, from
|
|
272
|
+
Step 2) — this is the page-level layout blueprint, not just this slice's marked fragment.
|
|
273
|
+
- Translate it 1:1 into JSX, same rule as Step 3 — but at each position carrying a
|
|
274
|
+
`data-em-mark-id`, render that mark's owning slice's component instead of the mockup's static
|
|
275
|
+
content there, passing whatever props it declares. Only substitute positions whose slice
|
|
276
|
+
component already exists; leave any not-yet-built slice's marked area as the mockup's static
|
|
277
|
+
placeholder until that slice is built and this step runs again for it.
|
|
278
|
+
- The page owns threading shared inputs (route/query params, the current session, etc.) down as
|
|
279
|
+
props to each slice component — a slice component still only knows its own declared props, not
|
|
280
|
+
the rest of the page or its siblings.
|
|
281
|
+
- If `src/pages/<ScreenTitle>.tsx` already exists, update only this slice's marked position;
|
|
282
|
+
leave every other position (already-wired slices, and untouched mockup content) exactly as is.
|
|
283
|
+
|
|
284
|
+
## Step 7 — Verify against slice.json
|
|
285
|
+
|
|
286
|
+
After writing the hook/component (and context/page, if any), confirm:
|
|
287
|
+
- Every prop matches a `command.fields[]` entry exactly (name + type) — no invented or missing
|
|
288
|
+
fields.
|
|
289
|
+
- The `postCommand` call uses `command.apiEndpoint` verbatim — or, when it was missing at build
|
|
290
|
+
time, the provisional `/api/mock/...` path noted in a comment (Step 1).
|
|
291
|
+
- At least one numbered sample exists under this slice's `samples/` folder, shaped exactly like
|
|
292
|
+
this component's props.
|
|
293
|
+
- If a screen was read in Step 2, the rendered JSX matches the marked (or whole, if unmarked)
|
|
294
|
+
HTML fragment structurally — no invented buttons/fields the mockup didn't show, nothing from
|
|
295
|
+
the mockup silently dropped.
|
|
296
|
+
- Nothing in this slice's folder is imported from another slice's folder, and nothing outside this
|
|
297
|
+
slice's folder imports from it except `src/pages/` (Step 6) — deleting the folder should delete
|
|
298
|
+
the feature cleanly.
|
|
299
|
+
|
|
300
|
+
## Quality gate
|
|
301
|
+
|
|
302
|
+
Run `npm run build` (`tsc -b && vite build`) and `npm run lint` (`oxlint`) — both must pass clean.
|
|
303
|
+
There is no component test runner configured in this stack yet; if a slice's `specifications[]`
|
|
304
|
+
demand behavioral tests, flag that gap via `request-feedback` rather than inventing a test setup
|
|
305
|
+
ad hoc.
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: build-state-view
|
|
3
|
+
description: Build a read-side React slice for the Supabase stack — a component tailored to its READMODEL(s), reading its table/view through the shared api.ts (never Supabase directly), which is what lets it also be tested/previewed in isolation from numbered samples/sample-N.json files
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Build State View Slice
|
|
7
|
+
|
|
8
|
+
> Before doing anything else, read the slice definition from
|
|
9
|
+
> `.build-kit/.slices/<contextSlug>/<sliceFolder>/slice.json`. This file (plus, when a screen is
|
|
10
|
+
> involved, the live screen node fetched in Step 2) is the **source of truth** for every field and
|
|
11
|
+
> data source — never invent a field, prop, or table name that isn't there.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## What a State View Slice is
|
|
16
|
+
|
|
17
|
+
One or more `READMODEL` elements (`slice.json.readmodels[]`) that display data back to the user.
|
|
18
|
+
Read models here are **tailor-made for one component** — the default is a strict 1:1 correlation
|
|
19
|
+
between a read model and the component that renders it. A single component may combine more than
|
|
20
|
+
one read model (e.g. a dashboard-style screen), but the reverse — one read model reused by several
|
|
21
|
+
different components — is rare; if the same read model turns up wired into more than one slice's
|
|
22
|
+
screen, that's unusual enough to double-check rather than assume is intentional.
|
|
23
|
+
|
|
24
|
+
This stack builds only the **frontend half**, and its read architecture is **direct table access,
|
|
25
|
+
not a custom read API**: a read model is typically a real, persisted Postgres table (a projection
|
|
26
|
+
the backend keeps up to date), protected by Row Level Security policies that already enforce which
|
|
27
|
+
rows a given user may see. That RLS enforcement is exactly what makes it correct — not merely
|
|
28
|
+
convenient — for a component to read it straight from Supabase, with no bespoke backend endpoint in
|
|
29
|
+
between, the same way `miro-eventmodeling` and this stack's other Supabase-backed components do.
|
|
30
|
+
|
|
31
|
+
That said, a component never calls `supabase.from(...)` (or imports `src/lib/supabase.ts`) itself —
|
|
32
|
+
it calls `queryReadModel(...)` from `src/lib/api.ts`, same as `build-state-change` components only
|
|
33
|
+
ever call `postCommand`. `queryReadModel` is the real Supabase call in production, but in mock mode
|
|
34
|
+
it serves a numbered sample instead (see Step 4) — routing every read through this one seam is what
|
|
35
|
+
makes a component testable/previewable in isolation, with zero Supabase project needed.
|
|
36
|
+
|
|
37
|
+
Assume a read model names a real persisted table unless its `comments[]`, `description`, or a
|
|
38
|
+
custom prompt on the slice explicitly says otherwise (e.g. it's actually a view joining several
|
|
39
|
+
tables, or computed on the fly) — check those before assuming a plain table lookup.
|
|
40
|
+
|
|
41
|
+
## Step 1 — Read slice.json
|
|
42
|
+
|
|
43
|
+
Extract:
|
|
44
|
+
- **title**, **context** — slice identity, used for file paths (see Step 3).
|
|
45
|
+
- **readmodels[]** — one entry per read model this slice's screen displays. Each `Element` carries:
|
|
46
|
+
- `title` — the read model's name, e.g. `"ActiveReservationView"`.
|
|
47
|
+
- `fields[]` — the exact and complete set of columns/values the component displays — no more,
|
|
48
|
+
no fewer.
|
|
49
|
+
- `apiEndpoint` — in this stack, **the Postgres table or view name to query**, not a REST path.
|
|
50
|
+
**Read it literally from slice.json when it's present** — never invent or guess a table name
|
|
51
|
+
while a real one exists. **When it's missing** (no table/view decided yet), flag it but keep
|
|
52
|
+
building — this is a partial variant of `request-feedback`, not a full stop:
|
|
53
|
+
1. Post a comment on this slice node naming the read model and stating the table/view is missing
|
|
54
|
+
(`mcp__eventmodelers__add_comment`, same call `request-feedback` Step 3 uses), and mark the
|
|
55
|
+
slice `Blocked` (`mcp__eventmodelers__update_slice_status`, same as `request-feedback` Step 4).
|
|
56
|
+
2. Unlike a normal `request-feedback` escalation, **do not stop here** — continue on to build the
|
|
57
|
+
component exactly as normal, but against a mocked read: give `queryReadModel` a clearly
|
|
58
|
+
provisional table name, e.g. `mock_<read_model_title_snake_case>`, with a one-line comment
|
|
59
|
+
marking it as a placeholder pending the real table/view. In mock mode `queryReadModel` never
|
|
60
|
+
touches Supabase regardless of the table name (see `src/lib/api.ts`), so the component is
|
|
61
|
+
fully functional offline either way — only make Step 4's samples do extra work in this case:
|
|
62
|
+
cover every `readmodel.fields[]` entry with realistic, meaningful values (never placeholders
|
|
63
|
+
like `"foo"`/`"test"`), plus whatever edge cases `specifications[]` call out, since those
|
|
64
|
+
samples are the only data this slice will see until a real table/view is wired in.
|
|
65
|
+
3. Leave the slice's status as `Blocked` when you finish (see this stack's `CLAUDE.md` "Building
|
|
66
|
+
a Slice" step 6) — the built, mocked component still needs a real table/view before it's done,
|
|
67
|
+
so don't flip it to `Done`.
|
|
68
|
+
- `description` — implementation hints (filtering rules, sort order, business meaning).
|
|
69
|
+
- **screens[]** — the actor `SCREEN` element(s) that display this data, if any (see Step 2).
|
|
70
|
+
- **specifications[]** — GWT scenarios/storyline; treat as acceptance criteria (e.g. "given no
|
|
71
|
+
active reservation, show an empty state").
|
|
72
|
+
|
|
73
|
+
> **Comments**: as in `build-state-change` — resolve consumed ones via the same node-comment
|
|
74
|
+
> endpoint.
|
|
75
|
+
|
|
76
|
+
## Step 2 — Read the screen, and especially its marks
|
|
77
|
+
|
|
78
|
+
Identical to `build-state-change` Step 2 — fetch the live screen node
|
|
79
|
+
(`mcp__eventmodelers__get_node`), find this slice's marked fragment in `meta.pages`/`meta.marks`
|
|
80
|
+
(or the whole page if unmarked), and treat it as the blueprint. The same `learn-styleguide` note
|
|
81
|
+
applies: prefer its tokens/classes over the mockup's ad hoc styling where they disagree.
|
|
82
|
+
|
|
83
|
+
Also resolve this screen's lane the same way — `mcp__eventmodelers__get_board_outline` on the
|
|
84
|
+
screen's chapter, match this node's `id` to get its `lane`, slugify it to `laneSlug` — skip only if
|
|
85
|
+
`screens[]` was empty in Step 1.
|
|
86
|
+
|
|
87
|
+
## Step 3 — Build the dedicated component
|
|
88
|
+
|
|
89
|
+
**File**: `src/slices/<contextSlug>/<laneSlug>/<sliceFolder>/<ReadModelTitle>.tsx` (omit
|
|
90
|
+
`<laneSlug>` only for the rare read model with no screen at all), `contextSlug`/`sliceFolder`/
|
|
91
|
+
`laneSlug` derived exactly as in `build-state-change`. If a component genuinely combines more than
|
|
92
|
+
one read model, name it after the screen/purpose rather than forcing one read model's title onto a
|
|
93
|
+
multi-read-model component.
|
|
94
|
+
|
|
95
|
+
**Markup**: same rule as `build-state-change` — translate the blueprint from Step 2 1:1 into JSX;
|
|
96
|
+
the mockup is the design, not a starting point for a redesign.
|
|
97
|
+
|
|
98
|
+
**Props**: whatever the component needs to *scope* its query (e.g. `customerId`) — passed in from
|
|
99
|
+
its parent, never read from a global store. The queried data itself is **not** a prop — the
|
|
100
|
+
component fetches it.
|
|
101
|
+
|
|
102
|
+
**Default — call `queryReadModel` from `src/lib/api.ts` inside the component**. Never `supabase.from(...)`
|
|
103
|
+
directly, never a raw `fetch`:
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
// ActiveReservationView.tsx
|
|
107
|
+
import { useEffect, useState } from 'react';
|
|
108
|
+
import { queryReadModel } from '../../../../lib/api'; // one less '../' if this read model has no lane segment
|
|
109
|
+
|
|
110
|
+
export interface ActiveReservationViewProps {
|
|
111
|
+
customerId: string;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
interface ActiveReservation {
|
|
115
|
+
status: string;
|
|
116
|
+
// ...remaining readmodel.fields[], typed per each Field.type
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const samples = import.meta.glob<{ default: ActiveReservation }>('./samples/sample-*.json', { eager: true });
|
|
120
|
+
const samplesByNumber = Object.fromEntries(
|
|
121
|
+
Object.entries(samples).map(([path, mod]) => [path.match(/sample-(\d+)\.json$/)![1], mod.default]),
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
export function ActiveReservationView({ customerId }: ActiveReservationViewProps) {
|
|
125
|
+
const [data, setData] = useState<ActiveReservation | null>(null);
|
|
126
|
+
const [loading, setLoading] = useState(true);
|
|
127
|
+
const [error, setError] = useState<string | null>(null);
|
|
128
|
+
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
let cancelled = false;
|
|
131
|
+
setLoading(true);
|
|
132
|
+
queryReadModel<ActiveReservation>(
|
|
133
|
+
{
|
|
134
|
+
table: 'active_reservations', // readmodel.apiEndpoint, verbatim
|
|
135
|
+
select: 'status, ...', // every readmodel.fields[] name, by its actual column name — not '*'
|
|
136
|
+
filters: { customer_id: customerId },
|
|
137
|
+
single: true,
|
|
138
|
+
},
|
|
139
|
+
samplesByNumber,
|
|
140
|
+
)
|
|
141
|
+
.then((result) => {
|
|
142
|
+
if (cancelled) return;
|
|
143
|
+
setData(result);
|
|
144
|
+
})
|
|
145
|
+
.catch((err) => {
|
|
146
|
+
if (cancelled) return;
|
|
147
|
+
setError(err instanceof Error ? err.message : 'Something went wrong');
|
|
148
|
+
})
|
|
149
|
+
.finally(() => {
|
|
150
|
+
if (!cancelled) setLoading(false);
|
|
151
|
+
});
|
|
152
|
+
return () => {
|
|
153
|
+
cancelled = true;
|
|
154
|
+
};
|
|
155
|
+
}, [customerId]);
|
|
156
|
+
|
|
157
|
+
// ...JSX translated from the marked screen fragment. Render a sensible empty/background
|
|
158
|
+
// state for `data === null` (or `[]`), distinct from the loading and error states — a
|
|
159
|
+
// component must never assume data is always present...
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Select fields explicitly by name rather than `select('*')`. Map each field's `name` to its actual
|
|
164
|
+
column name — check the paired backend stack's migrations if the naming convention (e.g. camelCase
|
|
165
|
+
vs. snake_case) isn't obvious rather than guessing; if genuinely unclear, use `request-feedback`.
|
|
166
|
+
|
|
167
|
+
Extract a `use<ReadModelTitle>.ts` hook, or introduce a colocated `<Name>Context.tsx`, under
|
|
168
|
+
exactly the same conditions as `build-state-change` Steps 3-4 — only when genuinely warranted
|
|
169
|
+
(logic reused by more than one sibling component, or state that must be shared across them). Don't
|
|
170
|
+
reach for either by default; most read-model components are a query plus a render and need
|
|
171
|
+
nothing more.
|
|
172
|
+
|
|
173
|
+
## Step 4 — Add numbered samples
|
|
174
|
+
|
|
175
|
+
Every read-model component ships a `samples/` folder colocated next to it:
|
|
176
|
+
`src/slices/<contextSlug>/<laneSlug>/<sliceFolder>/samples/sample-<N>.json` (`sample-1.json`,
|
|
177
|
+
`sample-2.json`, ...). Each file mimics **exactly the row shape `queryReadModel` returns** — the
|
|
178
|
+
same field names/types as `readmodel.fields[]`, as a single object for `single: true` or an array
|
|
179
|
+
otherwise; use `null`/`[]` for a sample meant to exercise the empty/background state.
|
|
180
|
+
|
|
181
|
+
Add at least one sample (`sample-1.json`) covering the ordinary case from
|
|
182
|
+
`readmodel.fields[].example` values; add more numbered ones for whatever other scenarios this
|
|
183
|
+
read model's `specifications[]` call out (e.g. an empty result, a second customer's data). The
|
|
184
|
+
same number across *different* components' `samples/` folders is meant to represent one coherent
|
|
185
|
+
scenario across the whole app (e.g. every component's `sample-2.json` together tell the story of
|
|
186
|
+
"a long-time customer") — keep that in mind when choosing what a given number represents, rather
|
|
187
|
+
than numbering arbitrarily per component.
|
|
188
|
+
|
|
189
|
+
No extra wiring is needed beyond the `import.meta.glob` shown in Step 3 — switching which sample is
|
|
190
|
+
active for the *entire app* is just the `VITE_MOCK_SAMPLE` env var (see `.env.example`); this
|
|
191
|
+
component doesn't do anything with that number itself, `queryReadModel` reads it internally.
|
|
192
|
+
|
|
193
|
+
## Step 5 — Compose onto the shared page, if this screen's name is shared
|
|
194
|
+
|
|
195
|
+
Identical to `build-state-change` Step 5 — a screen's **title is the page name**, and the number
|
|
196
|
+
of *distinct* screen titles across the board is a direct hint at how many pages this app needs, not
|
|
197
|
+
one page per slice.
|
|
198
|
+
|
|
199
|
+
1. Check whether any other slice references a screen with the same `title` as this slice's screen,
|
|
200
|
+
across every context's local index:
|
|
201
|
+
```bash
|
|
202
|
+
grep -l '"title": *"<ScreenTitle>"' .build-kit/.slices/*/index.json
|
|
203
|
+
```
|
|
204
|
+
(Run `/load-slice` with no filter first if the board's other slices haven't been pulled locally
|
|
205
|
+
yet.)
|
|
206
|
+
2. **No match**: nothing further to do — this slice's component stands alone for now.
|
|
207
|
+
3. **Match found**: this screen is a shared page, composed at `src/pages/<ScreenTitle>.tsx` — the
|
|
208
|
+
one place allowed to import across slice folders. Translate the screen's whole, unmarked
|
|
209
|
+
`meta.pages` markup into JSX (the page-level layout), and at each `data-em-mark-id` position
|
|
210
|
+
render that mark's owning slice's component (only for slices already built; leave the rest as
|
|
211
|
+
the mockup's static content until they are). The page threads shared inputs down as props; a
|
|
212
|
+
slice component still only knows its own declared props. Update only this slice's marked
|
|
213
|
+
position if the page file already exists.
|
|
214
|
+
|
|
215
|
+
## Step 6 — Verify against slice.json
|
|
216
|
+
|
|
217
|
+
- Every rendered field traces back to a `readmodel.fields[]` entry — no invented or missing
|
|
218
|
+
fields.
|
|
219
|
+
- The `queryReadModel` call targets `readmodel.apiEndpoint` verbatim as `table` — or, when it was
|
|
220
|
+
missing at build time, the provisional `mock_...` table name noted in a comment (Step 1) — never a
|
|
221
|
+
raw `fetch`, a direct `supabase.from(...)` call, or a hardcoded table name that doesn't match
|
|
222
|
+
slice.json.
|
|
223
|
+
- At least one numbered sample exists under this slice's `samples/` folder, shaped exactly like
|
|
224
|
+
`queryReadModel`'s return value.
|
|
225
|
+
- The component renders a sensible empty/background state, distinct from loading/error, when the
|
|
226
|
+
data is `null`/`[]`.
|
|
227
|
+
- If a screen was read in Step 2, the rendered JSX matches the marked (or whole, if unmarked) HTML
|
|
228
|
+
fragment structurally.
|
|
229
|
+
- Nothing in this slice's folder is imported from another slice's folder, and nothing outside it
|
|
230
|
+
imports from it except `src/pages/` (Step 5) — deleting the folder should delete the feature
|
|
231
|
+
cleanly.
|
|
232
|
+
|
|
233
|
+
## Quality gate
|
|
234
|
+
|
|
235
|
+
Run `npm run build` (`tsc -b && vite build`) and `npm run lint` (`oxlint`) — both must pass clean.
|
|
236
|
+
There is no component test runner configured in this stack yet; if a slice's `specifications[]`
|
|
237
|
+
demand behavioral tests, flag that gap via `request-feedback` rather than inventing a test setup
|
|
238
|
+
ad hoc.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: init-style-guide
|
|
3
|
+
description: Interview the user for CSS, design tokens, brand documents, or any existing style guide material, and persist it as references inside the learn-styleguide skill so UI-generating skills (build-state-change, build-state-view) stay on-brand
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Init Style Guide
|
|
7
|
+
|
|
8
|
+
Run this whenever the user wants to give this project a design system to follow — "here's our
|
|
9
|
+
style guide", "add our brand CSS", "use this doc for the UI" — or wants to add more material to
|
|
10
|
+
one already started. This skill only collects and files material; it does not itself apply any
|
|
11
|
+
styling to code.
|
|
12
|
+
|
|
13
|
+
## Step 1 — Ask what they have
|
|
14
|
+
|
|
15
|
+
Ask the user what they'd like to provide. Accept any of:
|
|
16
|
+
- An existing CSS/SCSS file (a path) — design tokens, a component library stylesheet, a Tailwind
|
|
17
|
+
config, etc.
|
|
18
|
+
- A brand/style guide document (Markdown, PDF, plain text, a Figma export, ...) — a path.
|
|
19
|
+
- Pasted text/tokens directly in the conversation (e.g. a color palette, font stack, spacing
|
|
20
|
+
scale).
|
|
21
|
+
- A URL to a live, publicly reachable style guide page — only if the user gives the URL
|
|
22
|
+
themselves; never search for or guess one.
|
|
23
|
+
|
|
24
|
+
If they have nothing to add right now, stop — do not create empty placeholder files.
|
|
25
|
+
|
|
26
|
+
## Step 2 — Persist each item as a reference, verbatim
|
|
27
|
+
|
|
28
|
+
For every item provided, write it into `.claude/skills/learn-styleguide/references/`. **Never
|
|
29
|
+
summarize, rewrite, or supplement** what was given — this is a store of source material, not an
|
|
30
|
+
interpretation of it.
|
|
31
|
+
|
|
32
|
+
- **A file path** → copy the file as-is into `references/<original-filename>`, preserving its
|
|
33
|
+
extension (`.css`, `.scss`, `.md`, `.pdf`, ...).
|
|
34
|
+
- **A URL** → fetch it and save the raw content into `references/<slugified-title-or-host>.md`,
|
|
35
|
+
with a one-line header noting the source URL and the fetch date.
|
|
36
|
+
- **Pasted text/tokens** → write into `references/<short-slug>.md`, wrapped in a fenced code block
|
|
37
|
+
when it's CSS/code, with a one-line header noting what it is and the date provided.
|
|
38
|
+
|
|
39
|
+
Pick filenames that describe the content (`brand-colors.css`, `component-library.md`,
|
|
40
|
+
`voice-and-tone.pdf`), never generic names like `input1`.
|
|
41
|
+
|
|
42
|
+
If `references/` already has files from a previous run, list them back to the user first so they
|
|
43
|
+
know what's already covered, then only add what's new. Never delete or overwrite an existing
|
|
44
|
+
reference unless the user confirms that specific file should be replaced.
|
|
45
|
+
|
|
46
|
+
## Step 3 — Refresh learn-styleguide's index
|
|
47
|
+
|
|
48
|
+
In `.claude/skills/learn-styleguide/SKILL.md`, replace the `## References` section's contents with
|
|
49
|
+
one bullet per file now in `references/` — filename plus a short (one-line) description of what it
|
|
50
|
+
contains, based on a quick read of the file, never invented. Leave everything else in that file
|
|
51
|
+
(frontmatter, the "How to use this skill" section) untouched.
|
|
52
|
+
|
|
53
|
+
## Step 4 — Confirm
|
|
54
|
+
|
|
55
|
+
Tell the user what was saved and where, e.g.:
|
|
56
|
+
```
|
|
57
|
+
Saved to .claude/skills/learn-styleguide/references/:
|
|
58
|
+
- brand-colors.css (CSS custom properties: primary/secondary palette, spacing scale)
|
|
59
|
+
- voice-and-tone.md (brand document — tone, terminology to avoid)
|
|
60
|
+
```
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: learn-styleguide
|
|
3
|
+
description: This project's visual style guide / design system reference material — read before generating or redesigning any UI (screens, components). Populated and kept up to date by the init-style-guide skill.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Style Guide References
|
|
7
|
+
|
|
8
|
+
## References
|
|
9
|
+
|
|
10
|
+
_None yet — run `init-style-guide` to add CSS, design tokens, or brand documents._
|
|
11
|
+
|
|
12
|
+
## How to use this skill
|
|
13
|
+
|
|
14
|
+
Before turning a marked screen fragment into a component (`build-state-change`/`build-state-view`
|
|
15
|
+
Step 2), or designing any new screen, read whichever file(s) under `references/` are relevant to
|
|
16
|
+
the task at hand — colors/typography/spacing before styling, a component-library doc before
|
|
17
|
+
choosing markup patterns, a voice-and-tone doc before writing copy.
|
|
18
|
+
|
|
19
|
+
Prefer these references over values invented on the spot, and prefer them over an existing screen
|
|
20
|
+
mockup's own ad hoc inline styles when the two disagree — the mockup gives structure and content,
|
|
21
|
+
this skill gives the real visual language.
|
|
22
|
+
|
|
23
|
+
Do not invent references that aren't listed above. If the current task needs a design decision
|
|
24
|
+
these references don't cover, fall back to what the mockup/task already gives you rather than
|
|
25
|
+
guessing a brand rule — or ask the user, if the choice materially affects business logic.
|
|
26
|
+
|
|
27
|
+
This skill's content is entirely sourced from what the user provided via `init-style-guide` —
|
|
28
|
+
never add, rewrite, or supplement it with invented material.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# References
|
|
2
|
+
|
|
3
|
+
Source material dropped here by `init-style-guide`, verbatim — CSS/SCSS files, brand or style
|
|
4
|
+
guide documents, saved pages, or pasted design tokens. Each file is exactly what the user provided;
|
|
5
|
+
nothing here is generated or summarized. See `../SKILL.md` for how these get used.
|