odd-studio 1.0.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.
- package/.claude-plugin/plugin.json +19 -0
- package/README.md +229 -0
- package/bin/odd-studio.js +212 -0
- package/hooks/odd-destructive-guard.sh +98 -0
- package/hooks/odd-git-safety.sh +138 -0
- package/hooks/odd-outcome-quality.sh +84 -0
- package/hooks/odd-pre-build.sh +57 -0
- package/hooks/odd-session-save.sh +38 -0
- package/hooks/odd-ui-check.sh +69 -0
- package/package.json +43 -0
- package/scripts/install-skill.js +30 -0
- package/scripts/postinstall.js +28 -0
- package/scripts/scaffold-project.js +61 -0
- package/scripts/setup-hooks.js +105 -0
- package/skill/SKILL.md +464 -0
- package/skill/docs/build/build-protocol.md +532 -0
- package/skill/docs/kb/odd-kb.md +462 -0
- package/skill/docs/planning/build-planner.md +315 -0
- package/skill/docs/planning/outcome-writer.md +328 -0
- package/skill/docs/planning/persona-architect.md +258 -0
- package/skill/docs/planning/systems-mapper.md +270 -0
- package/skill/docs/ui/accessibility.md +415 -0
- package/skill/docs/ui/component-guide.md +356 -0
- package/skill/docs/ui/design-system.md +403 -0
- package/templates/.odd/state.json +16 -0
- package/templates/CLAUDE.md +93 -0
- package/templates/docs/contract-map.md +60 -0
- package/templates/docs/outcomes/.gitkeep +0 -0
- package/templates/docs/outcomes/example-outcome.md +104 -0
- package/templates/docs/personas/.gitkeep +0 -0
- package/templates/docs/personas/example-persona.md +108 -0
- package/templates/docs/plan.md +73 -0
- package/templates/docs/ui/.gitkeep +0 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
# ODD Studio — UI Excellence Layer
|
|
2
|
+
|
|
3
|
+
The UI is not the system. The UI is paint on top of the system.
|
|
4
|
+
|
|
5
|
+
This is the most important sentence in this document. Every choice in this guide flows from it. Your business logic — the rules about who can approve what, how fees are calculated, what triggers a notification — lives in your outcomes, your contracts, and your data layer. The UI's job is to make those capabilities accessible, legible, and pleasant to use. It contains zero business logic of its own.
|
|
6
|
+
|
|
7
|
+
When you hold this distinction clearly, UI work becomes simpler. You are not designing a system. You are designing a window into a system that already exists.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## The Default Stack
|
|
12
|
+
|
|
13
|
+
ODD Studio uses a specific set of tools for all UI work. These are not arbitrary choices — each one was selected for a reason that matters to non-technical builders.
|
|
14
|
+
|
|
15
|
+
### Next.js App Router
|
|
16
|
+
|
|
17
|
+
Next.js renders pages on the server and sends them to the browser pre-built. This means users see content quickly, and search engines can index public pages. The App Router is the current architecture — it uses React Server Components to keep heavy logic on the server and lightweight interaction code in the browser.
|
|
18
|
+
|
|
19
|
+
You do not need to understand React Server Components to use them. You need to know: if a component reads data from your database, it is a server component. If a component responds to user clicks or typing, it is a client component. Claude Code will handle the distinction.
|
|
20
|
+
|
|
21
|
+
### TypeScript
|
|
22
|
+
|
|
23
|
+
TypeScript is JavaScript with type annotations. Types are descriptions of the shape of your data — "this value is a string", "this object has a name and an email and a boolean called isApproved". TypeScript catches contradictions before they reach users. If a UI component expects a student name and receives an empty value, TypeScript flags it during build, not at 2am when a user reports a bug.
|
|
24
|
+
|
|
25
|
+
### Tailwind CSS v4
|
|
26
|
+
|
|
27
|
+
Tailwind provides pre-built utility classes that you apply directly to HTML elements. Instead of writing `color: blue; font-size: 14px; margin: 8px;`, you write `text-blue-600 text-sm m-2`. The classes are consistent across the project — `m-2` always means 8px, everywhere. This creates visual consistency without needing a custom CSS file.
|
|
28
|
+
|
|
29
|
+
Tailwind v4 uses CSS variables under the hood, which means your design tokens (colours, sizes, spacing) are defined once and flow through the entire UI automatically.
|
|
30
|
+
|
|
31
|
+
### shadcn/ui
|
|
32
|
+
|
|
33
|
+
shadcn/ui provides a library of beautiful, accessible components — buttons, forms, tables, dialogs, navigation — that you copy into your project and own directly. This is different from most UI libraries, which are black boxes you import and cannot modify. With shadcn, the component code lives in your codebase. You can change anything.
|
|
34
|
+
|
|
35
|
+
Each shadcn component is built on Radix UI primitives, which means keyboard navigation, focus management, and ARIA attributes are handled correctly without any extra work.
|
|
36
|
+
|
|
37
|
+
### Radix UI
|
|
38
|
+
|
|
39
|
+
Radix is the engine beneath shadcn. It handles the hard parts of accessible interactive components: dropdown menus that close when you press Escape, dialogs that trap focus correctly, sliders that respond to arrow keys. You will rarely interact with Radix directly — shadcn exposes it through a cleaner interface.
|
|
40
|
+
|
|
41
|
+
### Framer Motion
|
|
42
|
+
|
|
43
|
+
Framer Motion is an animation library for React. It makes the interface feel alive — list items that slide in when they appear, buttons that give subtle feedback when clicked, page transitions that guide the eye. Used well, animation communicates meaning: a success state, a loading state, an error state. Used poorly, animation is noise.
|
|
44
|
+
|
|
45
|
+
The rule for when to use Framer Motion is in the component selection guide below.
|
|
46
|
+
|
|
47
|
+
### How they work together
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
User visits a page
|
|
51
|
+
→ Next.js App Router renders the page on the server
|
|
52
|
+
→ Data from Prisma/PostgreSQL is fetched
|
|
53
|
+
→ Page is sent to browser as HTML
|
|
54
|
+
|
|
55
|
+
User sees the page
|
|
56
|
+
→ shadcn/ui components provide the visual structure
|
|
57
|
+
→ Tailwind classes provide the styling
|
|
58
|
+
→ Design tokens (colours, spacing) flow from tailwind.config
|
|
59
|
+
|
|
60
|
+
User interacts
|
|
61
|
+
→ Client components handle interaction
|
|
62
|
+
→ Framer Motion animates state changes
|
|
63
|
+
→ shadcn/Radix handles keyboard and focus behaviour
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Design Token System
|
|
69
|
+
|
|
70
|
+
Design tokens are the single source of truth for your visual language. When you define a primary colour as `hsl(221 83% 53%)`, every button, every link, every highlighted element in your entire application uses that colour. Changing one value changes everything.
|
|
71
|
+
|
|
72
|
+
### Setting up tokens in globals.css
|
|
73
|
+
|
|
74
|
+
```css
|
|
75
|
+
@layer base {
|
|
76
|
+
:root {
|
|
77
|
+
--background: 0 0% 100%;
|
|
78
|
+
--foreground: 222.2 84% 4.9%;
|
|
79
|
+
|
|
80
|
+
--primary: 221.2 83.2% 53.3%;
|
|
81
|
+
--primary-foreground: 210 40% 98%;
|
|
82
|
+
|
|
83
|
+
--secondary: 210 40% 96.1%;
|
|
84
|
+
--secondary-foreground: 222.2 47.4% 11.2%;
|
|
85
|
+
|
|
86
|
+
--accent: 210 40% 96.1%;
|
|
87
|
+
--accent-foreground: 222.2 47.4% 11.2%;
|
|
88
|
+
|
|
89
|
+
--destructive: 0 84.2% 60.2%;
|
|
90
|
+
--destructive-foreground: 210 40% 98%;
|
|
91
|
+
|
|
92
|
+
--muted: 210 40% 96.1%;
|
|
93
|
+
--muted-foreground: 215.4 16.3% 46.9%;
|
|
94
|
+
|
|
95
|
+
--border: 214.3 31.8% 91.4%;
|
|
96
|
+
--input: 214.3 31.8% 91.4%;
|
|
97
|
+
--ring: 221.2 83.2% 53.3%;
|
|
98
|
+
|
|
99
|
+
--radius: 0.5rem;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.dark {
|
|
103
|
+
--background: 222.2 84% 4.9%;
|
|
104
|
+
--foreground: 210 40% 98%;
|
|
105
|
+
/* dark mode values mirror the above */
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Typography scale
|
|
111
|
+
|
|
112
|
+
Use a maximum of 4 sizes and 2 weights across your entire UI. Fewer choices create more coherent designs.
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
text-sm — 14px — body copy, metadata, helper text
|
|
116
|
+
text-base — 16px — default body, form labels
|
|
117
|
+
text-lg — 18px — section headings, card titles
|
|
118
|
+
text-xl — 20px — page headings
|
|
119
|
+
text-2xl — 24px — primary page titles (use sparingly)
|
|
120
|
+
|
|
121
|
+
font-normal — body copy, descriptions
|
|
122
|
+
font-semibold — headings, labels, emphasis
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Resist the temptation to add `text-xs` for tiny metadata or `text-3xl` for hero text. Each exception weakens the rhythm of the design.
|
|
126
|
+
|
|
127
|
+
### Spacing
|
|
128
|
+
|
|
129
|
+
Tailwind's spacing scale is based on 4px increments. Use it consistently:
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
p-1 / m-1 — 4px — tight internal padding (badges, chips)
|
|
133
|
+
p-2 / m-2 — 8px — small gaps between related elements
|
|
134
|
+
p-4 / m-4 — 16px — standard internal padding (cards, form fields)
|
|
135
|
+
p-6 / m-6 — 24px — section separation
|
|
136
|
+
p-8 / m-8 — 32px — major layout divisions
|
|
137
|
+
p-12 / m-12 — 48px — page-level breathing room
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Border radius
|
|
141
|
+
|
|
142
|
+
Use `rounded-md` (6px) consistently for all interactive elements: buttons, inputs, cards, dialogs. Use `rounded-full` only for avatars and status indicators. Do not mix radius values — inconsistency reads as carelessness.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Component Selection Guide
|
|
147
|
+
|
|
148
|
+
When a UI outcome is being built, every element in the walkthrough should be mapped to a specific component before building begins. This prevents Claude Code from inventing components that do not match your design system.
|
|
149
|
+
|
|
150
|
+
### Data display
|
|
151
|
+
|
|
152
|
+
| Need | Component | Notes |
|
|
153
|
+
|------|-----------|-------|
|
|
154
|
+
| List of records | `Table` (shadcn) | Use for 5+ fields per record |
|
|
155
|
+
| Summary view | `Card` (shadcn) | Use for 2-4 fields, or when visual grouping matters |
|
|
156
|
+
| Status indicator | `Badge` (shadcn) | Pending / Active / Cancelled / Complete |
|
|
157
|
+
| Numeric summary | `Card` with large text | Dashboard stats, counts, totals |
|
|
158
|
+
| Hierarchical data | Nested `Card` or `Accordion` | Parent-child relationships |
|
|
159
|
+
|
|
160
|
+
### Forms
|
|
161
|
+
|
|
162
|
+
| Need | Component | Notes |
|
|
163
|
+
|------|-----------|-------|
|
|
164
|
+
| Short text | `Input` (shadcn) | Name, email, reference number |
|
|
165
|
+
| Long text | `Textarea` (shadcn) | Descriptions, notes, feedback |
|
|
166
|
+
| Choose one | `Select` or `RadioGroup` (shadcn) | Use Select for 5+ options |
|
|
167
|
+
| Choose multiple | `Checkbox` group (shadcn) | Use for 2-6 options |
|
|
168
|
+
| Toggle a setting | `Switch` (shadcn) | Binary on/off, not for data entry |
|
|
169
|
+
| Date | `DatePicker` (shadcn + react-day-picker) | Never use a plain text input for dates |
|
|
170
|
+
| File upload | `Input type="file"` styled | Always show file size limits |
|
|
171
|
+
|
|
172
|
+
### Navigation
|
|
173
|
+
|
|
174
|
+
| Need | Component | Notes |
|
|
175
|
+
|------|-----------|-------|
|
|
176
|
+
| Primary navigation | `Sidebar` (shadcn) | For authenticated app shells |
|
|
177
|
+
| Secondary navigation | `Tabs` (shadcn) | Within a page, between related views |
|
|
178
|
+
| Location context | `Breadcrumb` (shadcn) | For deep hierarchies (3+ levels) |
|
|
179
|
+
| User menu | `DropdownMenu` (shadcn) | Profile, settings, sign out |
|
|
180
|
+
| Page list | `NavigationMenu` (shadcn) | For marketing/public pages |
|
|
181
|
+
|
|
182
|
+
### Feedback and actions
|
|
183
|
+
|
|
184
|
+
| Need | Component | Notes |
|
|
185
|
+
|------|-----------|-------|
|
|
186
|
+
| Non-blocking notification | `Toast` (sonner) | Success, error, info after actions |
|
|
187
|
+
| Blocking message | `Alert` (shadcn) | Warning that requires attention before proceeding |
|
|
188
|
+
| Confirmation required | `Dialog` (shadcn) | Destructive actions, irreversible changes |
|
|
189
|
+
| Primary action | `Button variant="default"` | One per view |
|
|
190
|
+
| Secondary action | `Button variant="outline"` | Supporting actions |
|
|
191
|
+
| Destructive action | `Button variant="destructive"` | Delete, cancel, revoke |
|
|
192
|
+
| Disabled action | `Button disabled` | When action is not yet available |
|
|
193
|
+
|
|
194
|
+
### Loading states
|
|
195
|
+
|
|
196
|
+
| Situation | Use |
|
|
197
|
+
|-----------|-----|
|
|
198
|
+
| Content is loading | `Skeleton` (shadcn) — mirrors the shape of the content |
|
|
199
|
+
| Action is processing | `Button` with `Loader2` spinner icon, disabled |
|
|
200
|
+
| Page is transitioning | Framer Motion fade between pages |
|
|
201
|
+
| List is loading | Skeleton rows matching expected content shape |
|
|
202
|
+
|
|
203
|
+
Never use a blank white screen for loading. Never show a spinner with no surrounding context — the user cannot tell what is loading or how long it will take.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## When to Use Framer Motion
|
|
208
|
+
|
|
209
|
+
Framer Motion is a powerful tool that is easy to overuse. The test is: does this animation communicate meaning, or is it just movement?
|
|
210
|
+
|
|
211
|
+
**Use Framer Motion for:**
|
|
212
|
+
|
|
213
|
+
- Page transitions — a subtle fade-and-slide guides the eye from one view to the next
|
|
214
|
+
- List item entry — items that appear in a list one by one (staggered children) communicate that data is loading progressively
|
|
215
|
+
- Success states — a checkmark that scales in confirms the action completed
|
|
216
|
+
- Empty states — an illustration that fades in softens the jarring appearance of nothing
|
|
217
|
+
- Sidebar open/close — smooth panel transitions feel intentional, not mechanical
|
|
218
|
+
|
|
219
|
+
**Do not use Framer Motion for:**
|
|
220
|
+
|
|
221
|
+
- Hover effects on standard buttons (CSS transitions are sufficient and faster)
|
|
222
|
+
- Constant ambient animation (rotating elements, pulsing backgrounds)
|
|
223
|
+
- Anything that distracts from the content
|
|
224
|
+
|
|
225
|
+
**Always respect reduced motion:**
|
|
226
|
+
|
|
227
|
+
```tsx
|
|
228
|
+
import { useReducedMotion } from 'framer-motion';
|
|
229
|
+
|
|
230
|
+
const shouldReduceMotion = useReducedMotion();
|
|
231
|
+
|
|
232
|
+
const variants = {
|
|
233
|
+
hidden: { opacity: 0, y: shouldReduceMotion ? 0 : 8 },
|
|
234
|
+
visible: { opacity: 1, y: 0 },
|
|
235
|
+
};
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Users with vestibular disorders, epilepsy, or motion sensitivity may have configured their operating system to reduce motion. Framer Motion's `useReducedMotion` hook reads this preference. Always use it for any animation involving movement (translate, scale). Opacity-only transitions are generally safe to preserve.
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Mobile-First Verification Protocol
|
|
243
|
+
|
|
244
|
+
Every UI outcome is verified at mobile size first. If it works at 375px (the width of an iPhone SE, the smallest common smartphone), it works everywhere. If it only works at desktop width, it is not finished.
|
|
245
|
+
|
|
246
|
+
### Verification sequence
|
|
247
|
+
|
|
248
|
+
**1. Open browser DevTools and set viewport to 375px.**
|
|
249
|
+
|
|
250
|
+
In Chrome: press F12, then click the device icon in the top-left of DevTools. Set width to 375, height to 812.
|
|
251
|
+
|
|
252
|
+
**2. Run the mobile checklist:**
|
|
253
|
+
|
|
254
|
+
- [ ] All text is readable without zooming. No text smaller than 14px (Tailwind `text-sm`).
|
|
255
|
+
- [ ] All buttons and interactive elements have a minimum touch target of 44x44px.
|
|
256
|
+
- [ ] Form fields are large enough to tap accurately. Inputs at least 44px tall.
|
|
257
|
+
- [ ] No content is cut off or hidden behind other elements.
|
|
258
|
+
- [ ] No horizontal scroll. The page fits within 375px width.
|
|
259
|
+
- [ ] Navigation is accessible. On mobile, primary navigation should be in a bottom bar or a hamburger menu — never in a top sidebar that requires precise mouse targeting.
|
|
260
|
+
- [ ] The most important action on each screen is reachable with the right thumb (bottom half of screen).
|
|
261
|
+
|
|
262
|
+
**3. Verify at 768px (tablet).**
|
|
263
|
+
|
|
264
|
+
The layout may reflow. Two-column layouts may appear here that were single-column on mobile. Verify that the reflow is intentional and that content does not overlap.
|
|
265
|
+
|
|
266
|
+
**4. Verify at 1280px (desktop).**
|
|
267
|
+
|
|
268
|
+
Full layout. Sidebar navigation visible if applicable. Multi-column data tables visible. Verify that content does not stretch uncomfortably wide — apply `max-w-7xl mx-auto` to contain content on very wide screens.
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## WCAG 2.1 AA Checklist
|
|
273
|
+
|
|
274
|
+
Accessibility is not a nice-to-have. It is a legal requirement in most jurisdictions and a quality indicator. Inaccessible software tells users they were not thought of. The shadcn/Radix stack handles much of this automatically — but you must verify it.
|
|
275
|
+
|
|
276
|
+
Run this checklist after every UI outcome verification:
|
|
277
|
+
|
|
278
|
+
### Colour contrast
|
|
279
|
+
|
|
280
|
+
- [ ] Normal text (under 18px): minimum 4.5:1 contrast ratio against background
|
|
281
|
+
- [ ] Large text (18px or larger): minimum 3:1 contrast ratio
|
|
282
|
+
- [ ] Interactive elements (buttons, links): minimum 3:1 against adjacent colours
|
|
283
|
+
- Check using the browser's built-in accessibility inspector or https://webaim.org/resources/contrastchecker/
|
|
284
|
+
- The default shadcn colour tokens pass AA when used as intended. Violations occur when custom colours are introduced without checking contrast.
|
|
285
|
+
|
|
286
|
+
### Keyboard navigation
|
|
287
|
+
|
|
288
|
+
- [ ] Tab through every interactive element on the page in a logical order (top to bottom, left to right)
|
|
289
|
+
- [ ] Every interactive element is reachable by Tab
|
|
290
|
+
- [ ] No element traps focus (pressing Tab always moves to the next element)
|
|
291
|
+
- [ ] Modals and dialogs trap focus correctly (Tab cycles within the dialog only while it is open)
|
|
292
|
+
- [ ] Press Escape to close any overlay — dialogs, dropdowns, drawers
|
|
293
|
+
|
|
294
|
+
### Focus indicators
|
|
295
|
+
|
|
296
|
+
- [ ] A visible focus ring appears on every focused element
|
|
297
|
+
- Never set `outline: none` without providing an alternative focus indicator
|
|
298
|
+
- shadcn components use `ring` classes for focus — do not remove these
|
|
299
|
+
|
|
300
|
+
### Screen reader basics
|
|
301
|
+
|
|
302
|
+
- [ ] All images have meaningful `alt` text, or `alt=""` if decorative
|
|
303
|
+
- [ ] Icon-only buttons have `aria-label` attributes (e.g. `aria-label="Close"` on an X button)
|
|
304
|
+
- [ ] Form inputs have associated labels (either `<label>` elements or `aria-label`)
|
|
305
|
+
- [ ] Headings follow a logical hierarchy (one `h1` per page, `h2` for sections, `h3` for subsections)
|
|
306
|
+
- [ ] Status messages (loading, success, error) are announced to screen readers using `role="status"` or `aria-live`
|
|
307
|
+
|
|
308
|
+
### Error messages
|
|
309
|
+
|
|
310
|
+
- [ ] Error messages are specific: "Enter a valid email address" not "Invalid input"
|
|
311
|
+
- [ ] Error messages appear adjacent to the field that caused the error
|
|
312
|
+
- [ ] Errors are announced to screen readers (use `aria-describedby` to connect field to error message)
|
|
313
|
+
- [ ] The form does not clear on error — the user's input is preserved
|
|
314
|
+
|
|
315
|
+
### Form success
|
|
316
|
+
|
|
317
|
+
- [ ] A clear confirmation appears after a successful form submission
|
|
318
|
+
- [ ] The confirmation states what happened in domain language: "Application submitted" not "Success"
|
|
319
|
+
- [ ] If the user is redirected after success, the new page makes the result obvious
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## Outcome-to-Component Mapping Process
|
|
324
|
+
|
|
325
|
+
Before briefing Claude Code to build any UI outcome, run this mapping process. It takes 10 minutes and prevents hours of rework.
|
|
326
|
+
|
|
327
|
+
**Step 1: Read the walkthrough and list every distinct screen.**
|
|
328
|
+
|
|
329
|
+
A screen is any distinct view the persona can be on. The list page is one screen. The detail page is one screen. The create form is one screen.
|
|
330
|
+
|
|
331
|
+
**Step 2: For each screen, identify the layout pattern.**
|
|
332
|
+
|
|
333
|
+
- Dashboard: primary metrics + activity feed + quick actions
|
|
334
|
+
- Form: heading + fields + submit + validation feedback
|
|
335
|
+
- List + detail: filterable/sortable list on one side, selected item detail on the other
|
|
336
|
+
- Wizard: multi-step form with progress indicator, one step per screen
|
|
337
|
+
- Empty state: illustration + explanation + primary action
|
|
338
|
+
|
|
339
|
+
**Step 3: For each interactive element, select the shadcn component.**
|
|
340
|
+
|
|
341
|
+
Go through the walkthrough line by line. Every button, every field, every status indicator maps to a specific component from the selection guide above.
|
|
342
|
+
|
|
343
|
+
**Step 4: Map Contracts Exposed to UI components.**
|
|
344
|
+
|
|
345
|
+
The data this outcome makes available (Contracts Exposed) needs to be displayed somewhere. Map each piece of data to the component that displays it. This ensures Claude Code knows what data needs to come from where.
|
|
346
|
+
|
|
347
|
+
**Step 5: Write the UI brief.**
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## Example UI Brief Format
|
|
352
|
+
|
|
353
|
+
Use this format when briefing Claude Code for a UI outcome:
|
|
354
|
+
|
|
355
|
+
```
|
|
356
|
+
Build the UI for Outcome: [Outcome Name]
|
|
357
|
+
|
|
358
|
+
PERSONA: [Persona name and brief description]
|
|
359
|
+
|
|
360
|
+
SCREENS:
|
|
361
|
+
1. [Screen name] — layout pattern: [list/form/dashboard/etc]
|
|
362
|
+
2. [Screen name] — layout pattern: [list/form/dashboard/etc]
|
|
363
|
+
|
|
364
|
+
COMPONENT MAPPING:
|
|
365
|
+
- Application list: shadcn Table with columns [Name, Date, Status, Actions]
|
|
366
|
+
- Status column: shadcn Badge, variant based on status value (pending=secondary, approved=default, rejected=destructive)
|
|
367
|
+
- Approve button: shadcn Button variant="default", opens shadcn Dialog for confirmation
|
|
368
|
+
- Reject button: shadcn Button variant="destructive", opens shadcn Dialog with Textarea for reason
|
|
369
|
+
- Success feedback: sonner toast, "Application approved" / "Application rejected"
|
|
370
|
+
- Loading state: Skeleton rows while data fetches
|
|
371
|
+
|
|
372
|
+
DATA BINDING:
|
|
373
|
+
- Table rows bind to: applications[] from Contracts Consumed (application-list contract)
|
|
374
|
+
- Approve action calls: POST /api/applications/[id]/approve
|
|
375
|
+
- Reject action calls: POST /api/applications/[id]/reject with reason
|
|
376
|
+
|
|
377
|
+
TAILWIND CONVENTIONS:
|
|
378
|
+
- Page wrapper: max-w-7xl mx-auto px-4 sm:px-6 lg:px-8
|
|
379
|
+
- Card padding: p-6
|
|
380
|
+
- Section spacing: space-y-6
|
|
381
|
+
- Table header: text-sm font-semibold text-muted-foreground
|
|
382
|
+
|
|
383
|
+
MOBILE REQUIREMENTS:
|
|
384
|
+
- Table becomes a card list on mobile (< 640px)
|
|
385
|
+
- Each card shows: applicant name, date, status badge, and action buttons stacked
|
|
386
|
+
- Touch targets minimum 44px
|
|
387
|
+
|
|
388
|
+
ANIMATION:
|
|
389
|
+
- Table rows fade in using Framer Motion staggered children (0.05s delay between rows)
|
|
390
|
+
- Dialog open/close uses shadcn default animation (already handled)
|
|
391
|
+
- Respect useReducedMotion — disable stagger if preference set
|
|
392
|
+
|
|
393
|
+
ACCESSIBILITY:
|
|
394
|
+
- Table caption: "Student applications — [current filter]"
|
|
395
|
+
- Approve/reject buttons: aria-label includes applicant name ("Approve application from [name]")
|
|
396
|
+
- Dialog: focus moves to first interactive element when opened
|
|
397
|
+
- Toast: role="status" for success messages
|
|
398
|
+
|
|
399
|
+
Full walkthrough for reference:
|
|
400
|
+
[PASTE FULL OUTCOME WALKTHROUGH HERE]
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
This brief gives Claude Code everything it needs: what to build, what to use for each element, how the data connects, how to handle mobile, how to handle animation, and how to handle accessibility. It leaves nothing to interpretation.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0.0",
|
|
3
|
+
"projectName": "{{PROJECT_NAME}}",
|
|
4
|
+
"initialisedAt": null,
|
|
5
|
+
"lastSaved": null,
|
|
6
|
+
"lastCommit": null,
|
|
7
|
+
"planningPhase": "not-started",
|
|
8
|
+
"currentStep": null,
|
|
9
|
+
"personas": [],
|
|
10
|
+
"outcomes": [],
|
|
11
|
+
"contractsMapped": false,
|
|
12
|
+
"planApproved": false,
|
|
13
|
+
"sessionBriefExported": false,
|
|
14
|
+
"buildPhase": null,
|
|
15
|
+
"notes": ""
|
|
16
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# {{PROJECT_NAME}} — Claude Code Configuration
|
|
2
|
+
# Powered by ODD Studio (Outcome-Driven Development)
|
|
3
|
+
|
|
4
|
+
## Project Context
|
|
5
|
+
This project is built using Outcome-Driven Development. All build decisions flow from the
|
|
6
|
+
outcomes documented in `docs/outcomes/` and the plan in `docs/plan.md`.
|
|
7
|
+
|
|
8
|
+
Before starting any build session, read:
|
|
9
|
+
1. `docs/plan.md` — the Master Implementation Plan
|
|
10
|
+
2. `docs/outcomes/` — all outcome documents for the current phase
|
|
11
|
+
3. `.odd/state.json` — current project state
|
|
12
|
+
|
|
13
|
+
## ODD Build Rules (Always Enforced)
|
|
14
|
+
|
|
15
|
+
### Language
|
|
16
|
+
- Describe problems and verify results in **domain language only**
|
|
17
|
+
- Never explain what code does — explain what the *user experiences*
|
|
18
|
+
- When reporting failures: "the dietary requirement I entered doesn't appear on the dashboard"
|
|
19
|
+
NOT: "the database column is null"
|
|
20
|
+
|
|
21
|
+
### Build Sequence
|
|
22
|
+
- NEVER build an outcome whose dependencies are not yet verified
|
|
23
|
+
- ALWAYS build shared infrastructure before individual outcomes
|
|
24
|
+
- ALWAYS run the full verification walkthrough before marking an outcome complete
|
|
25
|
+
- ALWAYS commit after each verified outcome with message: "Outcome [N] [name] — verified"
|
|
26
|
+
|
|
27
|
+
### Verification Standard
|
|
28
|
+
- Every outcome must be verified by walking through the verification steps in a browser
|
|
29
|
+
- Pass = works exactly as the walkthrough describes
|
|
30
|
+
- Fail = describe the failure in domain language, fix, re-verify the ENTIRE outcome
|
|
31
|
+
- An outcome is not done until every verification step passes
|
|
32
|
+
|
|
33
|
+
### Git Discipline
|
|
34
|
+
- Commit after every verified outcome (not before)
|
|
35
|
+
- Commit message format: `Outcome [N] [name] — verified` or `Phase [X] complete`
|
|
36
|
+
- Never force-push. Never commit .env files. Never skip hooks.
|
|
37
|
+
- If tests fail, fix them — never use --no-verify
|
|
38
|
+
|
|
39
|
+
### Code Quality
|
|
40
|
+
- No hardcoded secrets, API keys, or credentials — use environment variables
|
|
41
|
+
- Validate user input at system boundaries
|
|
42
|
+
- No magic numbers — extract all constants to config
|
|
43
|
+
- Keep files under 500 lines
|
|
44
|
+
|
|
45
|
+
## UI Standards (Every UI Outcome)
|
|
46
|
+
- Use shadcn/ui components as the default component library
|
|
47
|
+
- Style with Tailwind CSS — no inline styles, no hardcoded colours
|
|
48
|
+
- Every interactive element must be keyboard-navigable
|
|
49
|
+
- Every image must have meaningful alt text
|
|
50
|
+
- Every form must work without JavaScript (progressive enhancement)
|
|
51
|
+
- Verify on 375px screen width before marking any UI outcome complete
|
|
52
|
+
- WCAG 2.1 AA is the minimum accessibility standard
|
|
53
|
+
|
|
54
|
+
## Stack Defaults (Override Only If Outcomes Require It)
|
|
55
|
+
- Frontend: Next.js (App Router) + TypeScript
|
|
56
|
+
- Styling: Tailwind CSS v4 + shadcn/ui
|
|
57
|
+
- Animation: Framer Motion
|
|
58
|
+
- Database: PostgreSQL via Prisma ORM
|
|
59
|
+
- Auth: NextAuth.js
|
|
60
|
+
- Payments: Stripe
|
|
61
|
+
- Email: Resend
|
|
62
|
+
- Deployment: Vercel
|
|
63
|
+
|
|
64
|
+
## Session Start Protocol
|
|
65
|
+
Every new Claude Code session begins with:
|
|
66
|
+
```
|
|
67
|
+
Read docs/plan.md and docs/outcomes/. We are in Phase [X].
|
|
68
|
+
Phase [A...] is complete and verified.
|
|
69
|
+
We are [starting/continuing] Outcome [N]: [name].
|
|
70
|
+
[If continuing: Stage [X] is complete. Continue from Stage [X+1].]
|
|
71
|
+
Confirm you understand the current state before we begin.
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Swarm Build Protocol (Ruflo)
|
|
75
|
+
When building multiple independent outcomes in the same phase, use ruflo for parallel agent coordination:
|
|
76
|
+
- Spawn a Coordinator agent to publish shared contracts before concurrent building
|
|
77
|
+
- Spawn specialist agents: Backend, UI, QA
|
|
78
|
+
- All agents report failures in domain language
|
|
79
|
+
- QA agent runs full verification walkthrough after each outcome
|
|
80
|
+
|
|
81
|
+
Available ruflo tools: mcp__ruflo__agent_spawn, mcp__ruflo__memory_store,
|
|
82
|
+
mcp__ruflo__memory_retrieve, mcp__ruflo__task_create, mcp__ruflo__coordination_sync
|
|
83
|
+
|
|
84
|
+
## File Organisation
|
|
85
|
+
- `docs/personas/` — persona documents (never delete)
|
|
86
|
+
- `docs/outcomes/` — outcome documents (never delete)
|
|
87
|
+
- `docs/plan.md` — Master Implementation Plan (update status after each outcome)
|
|
88
|
+
- `docs/contract-map.md` — contracts and dependency graph
|
|
89
|
+
- `docs/ui/` — UI specifications per outcome
|
|
90
|
+
- `docs/session-brief.md` — generated session brief for handoff
|
|
91
|
+
- `.odd/state.json` — project state (updated by hooks automatically)
|
|
92
|
+
- `src/` — all source code
|
|
93
|
+
- `tests/` — all test files
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Contract Map & Dependency Graph
|
|
2
|
+
> Generated by ODD Studio — complete this in Claude Code using `/odd` → `*contracts`
|
|
3
|
+
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## All Contracts
|
|
7
|
+
|
|
8
|
+
| Contract Name | Produced By | Consumed By | Key Attributes |
|
|
9
|
+
|---------------|-------------|-------------|----------------|
|
|
10
|
+
| _e.g. confirmed booking_ | _Outcome 1: Event Booking_ | _Outcome 3: Loyalty Points, Outcome 4: Dashboard_ | _customer, event, date, amount, attendee count_ |
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Handshake Map
|
|
15
|
+
|
|
16
|
+
_For every connection between outcomes: upstream → contract → downstream_
|
|
17
|
+
_Mark each as CONFIRMED (both sides explicit) or BROKEN (one side missing)_
|
|
18
|
+
|
|
19
|
+
| Connection | Status |
|
|
20
|
+
|-----------|--------|
|
|
21
|
+
| Outcome 1 → [contract name] → Outcome 3 | ⬜ UNCONFIRMED |
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Shared Contracts
|
|
26
|
+
_Contracts consumed by more than one outcome — must be pre-specified before concurrent building_
|
|
27
|
+
|
|
28
|
+
| Contract | Consumed By | Must Agree Before Building |
|
|
29
|
+
|----------|-------------|---------------------------|
|
|
30
|
+
| | | |
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Dependency Graph (Build Order)
|
|
35
|
+
|
|
36
|
+
**Phase A — Foundation (no dependencies):**
|
|
37
|
+
-
|
|
38
|
+
|
|
39
|
+
**Phase B (depends only on Phase A):**
|
|
40
|
+
- These outcomes are independent within Phase B and can be built in any order:
|
|
41
|
+
-
|
|
42
|
+
|
|
43
|
+
**Phase C (depends on Phase B):**
|
|
44
|
+
-
|
|
45
|
+
|
|
46
|
+
**Independent (can be built after Phase A, at any point):**
|
|
47
|
+
-
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Orphan Report
|
|
52
|
+
|
|
53
|
+
_Outcomes that consume contracts nobody has exposed yet_
|
|
54
|
+
|
|
55
|
+
| Outcome | Needs | Missing From |
|
|
56
|
+
|---------|-------|-------------|
|
|
57
|
+
| | | |
|
|
58
|
+
|
|
59
|
+
> ✓ No orphaned dependencies — all contracts are accounted for.
|
|
60
|
+
_(Delete the row above and add rows if orphans exist)_
|
|
File without changes
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
# EXAMPLE OUTCOME — Read this, then delete this file and create your own
|
|
3
|
+
# Use /odd → *outcome in Claude Code to write outcomes interactively
|
|
4
|
+
# Review Status: APPROVED (example)
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Outcome 1: Rachel Fills an Open Volunteer Shift
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Field 1: Persona
|
|
12
|
+
**Rachel** — the volunteer coordinator. Managing on her phone between other tasks, maximum 2 taps to start any action, zero tolerance for slowness during urgent windows.
|
|
13
|
+
|
|
14
|
+
See: `docs/personas/rachel-volunteer-coordinator.md`
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Field 2: Trigger
|
|
19
|
+
It is Wednesday afternoon. Rachel has just received a WhatsApp message from a volunteer cancelling their Saturday morning shift at the Northside site. The event is 3 days away and the shift has a minimum of 2 volunteers — it now has 1. Rachel needs to assign a replacement before Thursday afternoon, when she sends the pre-event confirmation messages.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Field 3: Walkthrough
|
|
24
|
+
|
|
25
|
+
Rachel opens the app on her phone. She sees a notification badge on the Shifts section — the system has already flagged the cancellation and marked the Saturday morning shift at Northside as understaffed.
|
|
26
|
+
|
|
27
|
+
She taps the notification. The app opens directly to the affected shift: "Saturday 14 June — Northside Morning (8:00–13:00)". She can see it currently has 1 volunteer confirmed (David Chen) and needs a minimum of 2. A red badge shows "1 below minimum."
|
|
28
|
+
|
|
29
|
+
She taps "Find a volunteer." The app shows her a filtered list of 7 volunteers who are:
|
|
30
|
+
- Available on Saturday morning (they haven't declined or been assigned elsewhere)
|
|
31
|
+
- Certified for food handling (required for this shift)
|
|
32
|
+
- Based within 5 miles of the Northside site
|
|
33
|
+
|
|
34
|
+
Each volunteer card shows their name, how many shifts they've done this year, their last shift date, and a green/amber/red availability indicator. Rachel can see that Amara Osei is available and local — she taps her name.
|
|
35
|
+
|
|
36
|
+
Amara's full profile slides in from the right. Rachel can see her certification dates, her availability pattern, and a note that she's done 3 Northside shifts before. Rachel taps "Assign to shift."
|
|
37
|
+
|
|
38
|
+
A confirmation dialog appears: "Assign Amara Osei to Saturday 14 June, Northside Morning?" with Confirm and Cancel buttons. Rachel taps Confirm.
|
|
39
|
+
|
|
40
|
+
The shift now shows 2 of 2 volunteers. The understaffed badge is gone. Amara receives an automatic WhatsApp message and email: "Hi Amara — you've been assigned to the Northside Morning shift on Saturday 14 June, 8:00–13:00. Parking is available at the rear entrance. Your shift leader is David Chen (07700 900123). Reply CONFIRM to accept or DECLINE if you can't make it."
|
|
41
|
+
|
|
42
|
+
Rachel sees a banner: "Assignment sent. Amara has 48 hours to confirm." The shift card now shows Amara's status as "Pending confirmation."
|
|
43
|
+
|
|
44
|
+
If Amara does not respond within 48 hours, Rachel receives a notification: "Amara has not confirmed the Saturday shift. Tap to find another volunteer or send a reminder." The shift returns to amber (understaffed warning) until Amara confirms or Rachel finds an alternative.
|
|
45
|
+
|
|
46
|
+
If Amara declines, Rachel receives an immediate notification and the shift returns to the "Find a volunteer" state, with Amara removed from the available list for this shift.
|
|
47
|
+
|
|
48
|
+
On Saturday morning, Rachel opens the event roster. She sees both David and Amara confirmed, with their arrival time, emergency contact, and certification status. She does not see their home addresses.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Field 4: Verification
|
|
53
|
+
|
|
54
|
+
1. Log in as Rachel. Open the Shifts section. Confirm there is a notification badge for the cancelled shift.
|
|
55
|
+
2. Tap the notification. Confirm the app opens directly to the affected shift (not a generic shifts list).
|
|
56
|
+
3. Confirm the shift shows "1 below minimum" in red.
|
|
57
|
+
4. Tap "Find a volunteer." Confirm the list shows only volunteers who are (a) available Saturday morning, (b) food-handling certified, and (c) within 5 miles of Northside. Assign a test volunteer who fails one of these criteria and confirm they do NOT appear.
|
|
58
|
+
5. Tap a volunteer. Confirm the profile shows certifications, availability pattern, and shift history — but NOT their home address.
|
|
59
|
+
6. Assign a volunteer. Confirm the confirmation dialog appears before the assignment is made.
|
|
60
|
+
7. Confirm the assignment. Confirm the shift now shows 2 of 2 and the red badge is gone.
|
|
61
|
+
8. Confirm the assigned volunteer receives a WhatsApp/email notification with shift time, location, parking, and shift leader contact.
|
|
62
|
+
9. Wait 48 hours (or simulate in test data). Confirm Rachel receives a reminder notification if the volunteer has not confirmed.
|
|
63
|
+
10. Simulate a decline. Confirm Rachel is notified immediately and the shift returns to understaffed state.
|
|
64
|
+
11. Open the event roster as Rachel on Saturday morning. Confirm both volunteers appear with confirmation status and certifications. Confirm home addresses are NOT visible.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Field 5: Contracts Exposed
|
|
69
|
+
|
|
70
|
+
**Confirmed volunteer assignment:**
|
|
71
|
+
- Volunteer (name, certification status, shift history)
|
|
72
|
+
- Shift (date, time, site, shift leader)
|
|
73
|
+
- Assignment status (pending / confirmed / declined)
|
|
74
|
+
- Notification sent timestamp
|
|
75
|
+
- Confirmation deadline
|
|
76
|
+
|
|
77
|
+
Consumed by:
|
|
78
|
+
- Outcome 3: Event roster generation (site managers view)
|
|
79
|
+
- Outcome 4: Volunteer hours tracking (Rachel's monthly report)
|
|
80
|
+
- Outcome 5: Certification expiry alerts (uses certification dates from assignments)
|
|
81
|
+
|
|
82
|
+
**Updated shift fill status:**
|
|
83
|
+
- Shift ID, site, date
|
|
84
|
+
- Current fill count vs minimum
|
|
85
|
+
- Understaffed flag (true/false)
|
|
86
|
+
|
|
87
|
+
Consumed by:
|
|
88
|
+
- Outcome 2: Event dashboard (Rachel sees all shifts across all events at a glance)
|
|
89
|
+
- Outcome 6: Pre-event confirmation send (only triggers when all shifts are filled)
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Field 6: Dependencies
|
|
94
|
+
|
|
95
|
+
- **Volunteer profile with certifications** — from Outcome 0: Volunteer Registration. A volunteer must exist in the system with their certification status before they can appear in the filtered assignment list.
|
|
96
|
+
- **Shift definition** — from shared infrastructure (Phase A): events and their shifts must be created before they can be filled. The Saturday shift at Northside must exist as a record.
|
|
97
|
+
- **Availability tracking** — from Outcome 0: Volunteer Registration captures availability patterns. This outcome consumes that data for the filter.
|
|
98
|
+
- **Notification service** — Phase A shared infrastructure: WhatsApp/email sending capability must be set up before assignment notifications can be sent.
|
|
99
|
+
|
|
100
|
+
No dependency on any other outcome in Phase B — this outcome can be built as soon as Phase A and Outcome 0 (Volunteer Registration) are verified.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
*This example shows what a complete, approved outcome looks like. Note: specific (Rachel, not "the coordinator"), sequential walkthrough (each action triggers the next), failure paths included (non-response, decline), verification steps physically performable in a browser, contracts named in domain language.*
|