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,356 @@
|
|
|
1
|
+
# ODD UI Component Guide
|
|
2
|
+
## Selecting the Right Component for Every Outcome
|
|
3
|
+
|
|
4
|
+
This guide maps common user interface needs — as described in outcome walkthroughs — to the specific shadcn/ui components that serve them best. Use this when briefing the UI agent for any outcome.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## How to Use This Guide
|
|
9
|
+
|
|
10
|
+
When you have a UI outcome walkthrough, read each step and ask: "What does the persona *see* here, and what can they *do*?" Match the answer to a pattern below. The UI agent will implement the component — your job is to name the right one.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Data Display Patterns
|
|
15
|
+
|
|
16
|
+
### "She sees a list of [things]"
|
|
17
|
+
**Component: Table or Card Grid**
|
|
18
|
+
|
|
19
|
+
Use **Table** when:
|
|
20
|
+
- The persona needs to compare values across rows (e.g. all open bookings, all volunteers sorted by availability)
|
|
21
|
+
- There are 4+ columns of data
|
|
22
|
+
- The persona needs to sort or filter
|
|
23
|
+
- Example: "Rachel sees the twelve shifts sorted by fill status"
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
shadcn: Table, TableHeader, TableRow, TableCell
|
|
27
|
+
Add: sortable columns via @tanstack/react-table
|
|
28
|
+
Tailwind: table-auto w-full
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Use **Card Grid** when:
|
|
32
|
+
- Each item has a visual identity (photo, icon, colour indicator)
|
|
33
|
+
- The persona is browsing, not comparing
|
|
34
|
+
- Items have a clear primary action (Book, Assign, Edit)
|
|
35
|
+
- Example: "Helen sees the upcoming events with cover images and remaining capacity"
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
shadcn: Card, CardHeader, CardContent, CardFooter
|
|
39
|
+
Tailwind: grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
### "She sees a status indicator"
|
|
45
|
+
**Component: Badge**
|
|
46
|
+
|
|
47
|
+
Map domain statuses to Badge variants:
|
|
48
|
+
- **default** (neutral grey) — pending, draft, not started
|
|
49
|
+
- **secondary** (light) — in progress, assigned, scheduled
|
|
50
|
+
- **destructive** (red) — overdue, failed, blocked, urgent
|
|
51
|
+
- **outline** (bordered) — completed, verified, confirmed
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
shadcn: Badge variant="destructive" | "default" | "secondary" | "outline"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Never use raw colour classes for status — always use Badge so status is consistent everywhere.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
### "She sees a summary at the top of the page"
|
|
62
|
+
**Component: Stat Cards**
|
|
63
|
+
|
|
64
|
+
For dashboards showing counts, totals, or key metrics:
|
|
65
|
+
```
|
|
66
|
+
shadcn: Card with large number + label + optional trend indicator
|
|
67
|
+
Pattern: 3–4 stat cards in a responsive grid above the main table
|
|
68
|
+
Tailwind: grid grid-cols-2 lg:grid-cols-4 gap-4 mb-6
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
### "She sees the full record for one [thing]"
|
|
74
|
+
**Component: Detail Panel or Drawer**
|
|
75
|
+
|
|
76
|
+
Use **Drawer** when:
|
|
77
|
+
- The persona stays in context (doesn't leave the list view)
|
|
78
|
+
- The detail is a quick reference or quick action
|
|
79
|
+
- Example: "She clicks a booking and sees the full details without leaving the dashboard"
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
shadcn: Sheet (right-side drawer)
|
|
83
|
+
Trigger: clicking a row in the Table
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Use **dedicated page** when:
|
|
87
|
+
- There's a complex workflow attached to the record
|
|
88
|
+
- The persona will spend significant time here
|
|
89
|
+
- Example: "She opens the event page and manages all its shifts"
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
Next.js: app/[resource]/[id]/page.tsx
|
|
93
|
+
shadcn: Tabs to organise sections of a complex record
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Form Patterns
|
|
99
|
+
|
|
100
|
+
### "She fills in [information]"
|
|
101
|
+
**Component: Form with react-hook-form + zod**
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
shadcn: Form, FormField, FormItem, FormLabel, FormControl, FormMessage
|
|
105
|
+
Validation: zod schema matching the outcome's domain rules
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Field type selection:**
|
|
109
|
+
| Domain need | Component |
|
|
110
|
+
|-------------|-----------|
|
|
111
|
+
| Short text (name, title) | Input |
|
|
112
|
+
| Long text (notes, description) | Textarea |
|
|
113
|
+
| Choose from fixed options (< 8) | Select or RadioGroup |
|
|
114
|
+
| Choose from fixed options (> 8, searchable) | Combobox |
|
|
115
|
+
| Yes/no toggle | Switch |
|
|
116
|
+
| Agree/acknowledge | Checkbox |
|
|
117
|
+
| Date selection | Calendar + Popover (shadcn DatePicker pattern) |
|
|
118
|
+
| File upload | custom Input type="file" with drag-drop zone |
|
|
119
|
+
| Multiple selections | Multi-select Combobox or CheckboxGroup |
|
|
120
|
+
|
|
121
|
+
**Always:**
|
|
122
|
+
- Every form field has a visible label (not just placeholder text)
|
|
123
|
+
- Error messages are field-specific ("Enter a valid email address" not "Invalid")
|
|
124
|
+
- Required fields are marked (asterisk + screen-reader text)
|
|
125
|
+
- Submit button is disabled while submitting (shows loading state)
|
|
126
|
+
- Success state is clear — either navigate away or show a success Toast
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
### "She submits and sees confirmation"
|
|
131
|
+
**Component: Toast (Sonner)**
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
shadcn: Toaster + toast() from sonner
|
|
135
|
+
Success: toast.success("Booking confirmed — Marcus has been notified")
|
|
136
|
+
Error: toast.error("Could not save — Helen has been alerted")
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Toasts are for transient confirmations. Use a dedicated success screen or redirect for significant completions (payment confirmed, account created).
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
### "She fills in a multi-step process"
|
|
144
|
+
**Component: Step wizard with Progress indicator**
|
|
145
|
+
|
|
146
|
+
When a walkthrough has 3+ distinct stages:
|
|
147
|
+
```
|
|
148
|
+
shadcn: no built-in wizard — use Steps pattern:
|
|
149
|
+
- State machine with current step index
|
|
150
|
+
- Progress bar: Progress component showing % complete
|
|
151
|
+
- Navigation: Back/Next buttons with validation before advancing
|
|
152
|
+
- Each step is a separate component
|
|
153
|
+
- State persists across steps (react-hook-form with persist)
|
|
154
|
+
Tailwind: divide the form into clearly named sections
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Navigation Patterns
|
|
160
|
+
|
|
161
|
+
### "She moves between sections of the platform"
|
|
162
|
+
**Component: Sidebar navigation**
|
|
163
|
+
|
|
164
|
+
For authenticated apps with multiple sections:
|
|
165
|
+
```
|
|
166
|
+
shadcn: no built-in sidebar — use AppSidebar pattern:
|
|
167
|
+
- Fixed left sidebar on desktop (w-64)
|
|
168
|
+
- Sheet (drawer) on mobile (hamburger trigger)
|
|
169
|
+
- Active route highlighted
|
|
170
|
+
- User profile at bottom
|
|
171
|
+
- Collapsed icon-only mode on medium screens
|
|
172
|
+
Tailwind: hidden md:flex for desktop, Sheet for mobile
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
### "She moves between views of the same record"
|
|
178
|
+
**Component: Tabs**
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
shadcn: Tabs, TabsList, TabsTrigger, TabsContent
|
|
182
|
+
Max 5 tabs — more than 5 means the record needs a different structure
|
|
183
|
+
Keep the active tab in the URL (searchParams) so refresh preserves position
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
### "She returns to where she was"
|
|
189
|
+
**Component: Breadcrumb**
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
shadcn: Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbSeparator
|
|
193
|
+
Show: Home > Section > Record name
|
|
194
|
+
Always include a link back to the list view
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Feedback & State Patterns
|
|
200
|
+
|
|
201
|
+
### "She waits while the system processes"
|
|
202
|
+
**Component: Skeleton + Spinner**
|
|
203
|
+
|
|
204
|
+
Use **Skeleton** for initial page/data loading:
|
|
205
|
+
```
|
|
206
|
+
shadcn: Skeleton
|
|
207
|
+
Match the shape of the content that will appear
|
|
208
|
+
Show 3–5 skeleton items in a list, not a spinner
|
|
209
|
+
Tailwind: animate-pulse on Skeleton
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Use **Spinner/Loading state** for button actions (submitting, saving):
|
|
213
|
+
```
|
|
214
|
+
Button: disabled + loading state while action runs
|
|
215
|
+
shadcn: Button disabled with Loader2 icon (lucide-react) + "Saving..." text
|
|
216
|
+
Never remove the button — disable and show progress
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
### "Nothing is here yet"
|
|
222
|
+
**Component: Empty State**
|
|
223
|
+
|
|
224
|
+
Every list view needs an empty state — what the persona sees when there's no data yet:
|
|
225
|
+
```
|
|
226
|
+
Pattern: centred illustration/icon + heading + description + primary action button
|
|
227
|
+
Framer Motion: subtle fade-in (opacity 0 → 1, y 10 → 0, duration 0.3s)
|
|
228
|
+
Content: explain WHY it's empty and WHAT to do — not just "No items found"
|
|
229
|
+
Example: "No shifts assigned yet — click Add Shift to get started"
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
### "Something went wrong"
|
|
235
|
+
**Component: Alert (inline) or Toast (transient)**
|
|
236
|
+
|
|
237
|
+
Use **Alert** for persistent errors that block the workflow:
|
|
238
|
+
```
|
|
239
|
+
shadcn: Alert variant="destructive"
|
|
240
|
+
Include: what went wrong (domain language) + what to do next
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Use **Toast** for transient failures that don't block:
|
|
244
|
+
```
|
|
245
|
+
toast.error("Could not send confirmation — check your email settings")
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Never show raw error messages to users. Translate every technical error into domain language before displaying it.
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
### "She needs to confirm a destructive action"
|
|
253
|
+
**Component: AlertDialog**
|
|
254
|
+
|
|
255
|
+
For irreversible actions (delete, cancel, archive):
|
|
256
|
+
```
|
|
257
|
+
shadcn: AlertDialog, AlertDialogTrigger, AlertDialogContent,
|
|
258
|
+
AlertDialogTitle, AlertDialogDescription,
|
|
259
|
+
AlertDialogAction, AlertDialogCancel
|
|
260
|
+
Title: "Delete this booking?"
|
|
261
|
+
Description: Explain the consequence in domain terms
|
|
262
|
+
Action button: "destructive" variant ("Yes, delete booking")
|
|
263
|
+
Cancel: prominent — make it easy to back out
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Animation Patterns (Framer Motion)
|
|
269
|
+
|
|
270
|
+
### When to animate
|
|
271
|
+
|
|
272
|
+
| Situation | Animation | Duration |
|
|
273
|
+
|-----------|-----------|----------|
|
|
274
|
+
| Page/route transition | Fade in (opacity 0→1) | 200ms |
|
|
275
|
+
| List item appears | Slide up + fade (y:10→0, opacity:0→1) | 150ms |
|
|
276
|
+
| Modal/drawer opens | Already handled by shadcn/Radix | |
|
|
277
|
+
| Success state | Scale pulse (scale:1→1.05→1) | 300ms |
|
|
278
|
+
| Empty state | Gentle fade in | 250ms |
|
|
279
|
+
| Error shake | Horizontal shake (x: 0,−8,8,−8,8,0) | 400ms |
|
|
280
|
+
| Loading→content | Fade out skeleton, fade in content | 150ms |
|
|
281
|
+
|
|
282
|
+
### Always respect reduced motion
|
|
283
|
+
```tsx
|
|
284
|
+
import { useReducedMotion } from "framer-motion"
|
|
285
|
+
|
|
286
|
+
const prefersReducedMotion = useReducedMotion()
|
|
287
|
+
|
|
288
|
+
const variants = {
|
|
289
|
+
initial: { opacity: 0, y: prefersReducedMotion ? 0 : 10 },
|
|
290
|
+
animate: { opacity: 1, y: 0 },
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### What NOT to animate
|
|
295
|
+
- Navigation between pages on slow connections (delays feel like bugs)
|
|
296
|
+
- Form field focus states (use CSS :focus-visible instead)
|
|
297
|
+
- Data loading (use Skeleton, not animated spinners)
|
|
298
|
+
- Anything that moves continuously without user interaction
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## Responsive Breakpoints (Tailwind)
|
|
303
|
+
|
|
304
|
+
```
|
|
305
|
+
sm: 640px — most phones landscape
|
|
306
|
+
md: 768px — tablets, small laptops
|
|
307
|
+
lg: 1024px — standard laptops
|
|
308
|
+
xl: 1280px — desktop
|
|
309
|
+
|
|
310
|
+
Mobile-first: write base styles for 375px, use sm:/md:/lg: to add complexity
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
**Common responsive patterns:**
|
|
314
|
+
```
|
|
315
|
+
Navigation: hidden md:flex (sidebar) | flex md:hidden (mobile menu)
|
|
316
|
+
Grid: grid-cols-1 sm:grid-cols-2 lg:grid-cols-3
|
|
317
|
+
Text size: text-sm md:text-base
|
|
318
|
+
Padding: p-4 md:p-6 lg:p-8
|
|
319
|
+
Full-width CTA: w-full sm:w-auto (button fills screen on mobile)
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## The UI Outcome Brief Format
|
|
325
|
+
|
|
326
|
+
When handing a UI outcome to the UI agent, structure the brief as:
|
|
327
|
+
|
|
328
|
+
```
|
|
329
|
+
Build Outcome [N]: [name] — UI layer
|
|
330
|
+
|
|
331
|
+
OUTCOME WALKTHROUGH: [paste full walkthrough]
|
|
332
|
+
|
|
333
|
+
COMPONENT SELECTIONS:
|
|
334
|
+
Screen 1 — [name]:
|
|
335
|
+
Layout: [dashboard / form / list+detail / wizard]
|
|
336
|
+
Components: [list from this guide]
|
|
337
|
+
Data from contracts: [which fields from which upstream outcome]
|
|
338
|
+
Mobile behaviour: [how this adapts at 375px]
|
|
339
|
+
|
|
340
|
+
Screen 2 — [name]:
|
|
341
|
+
[same structure]
|
|
342
|
+
|
|
343
|
+
VERIFICATION (UI-specific additions):
|
|
344
|
+
[ ] Verify at 375px — all content readable, no horizontal scroll
|
|
345
|
+
[ ] Keyboard tab order makes sense on every screen
|
|
346
|
+
[ ] Loading states visible for every async action
|
|
347
|
+
[ ] Empty states present for every list
|
|
348
|
+
[ ] Error states present for every form
|
|
349
|
+
|
|
350
|
+
UI STANDARDS:
|
|
351
|
+
- shadcn/ui components only (no custom component library)
|
|
352
|
+
- Tailwind classes only (no inline styles)
|
|
353
|
+
- Framer Motion for: [list the specific animations needed]
|
|
354
|
+
- WCAG 2.1 AA required
|
|
355
|
+
- Reduce motion respected via useReducedMotion()
|
|
356
|
+
```
|