procedure-cli 1.0.2 → 1.0.3
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 +1 -0
- package/config/stacks/ink-tui.json +16 -0
- package/dist/lib/template.d.ts +2 -1
- package/dist/lib/template.js +20 -2
- package/dist/lib/template.js.map +1 -1
- package/dist/steps/generation.js +9 -2
- package/dist/steps/generation.js.map +1 -1
- package/dist/steps/product-context.js +1 -0
- package/dist/steps/product-context.js.map +1 -1
- package/dist/steps/stack-style.js +26 -0
- package/dist/steps/stack-style.js.map +1 -1
- package/package.json +1 -1
- package/templates/AGENTS.md.hbs +4 -0
- package/templates/CLAUDE.md.hbs +50 -0
- package/templates/README.md.hbs +10 -0
- package/templates/ink-tui/INK-TUI-TECH-SPECS.md +375 -0
- package/templates/ink-tui/INK-TUI-UI-UX-GUIDELINE.md +1070 -0
|
@@ -0,0 +1,1070 @@
|
|
|
1
|
+
# B3AWESOME CLI App UI/UX Guideline
|
|
2
|
+
|
|
3
|
+
A comprehensive design system and UI/UX specification for building terminal-based CLI applications using **React + Ink.js**. This document captures every visual pattern, interaction model, and component specification so that new projects can reproduce the same polished CLI experience.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
1. [Design Philosophy](#1-design-philosophy)
|
|
10
|
+
2. [Color System & Theme Architecture](#2-color-system--theme-architecture)
|
|
11
|
+
3. [Typography & Text Hierarchy](#3-typography--text-hierarchy)
|
|
12
|
+
4. [Layout System — The Gutter Pattern](#4-layout-system--the-gutter-pattern)
|
|
13
|
+
5. [Screen Anatomy](#5-screen-anatomy)
|
|
14
|
+
6. [Navigation Patterns](#6-navigation-patterns)
|
|
15
|
+
7. [Wizard & Timeline Pattern](#7-wizard--timeline-pattern)
|
|
16
|
+
8. [Form Patterns](#8-form-patterns)
|
|
17
|
+
9. [Component Library](#9-component-library)
|
|
18
|
+
10. [Welcome & Instruction Pattern](#10-welcome--instruction-pattern)
|
|
19
|
+
11. [Confirmation & Destructive Actions](#11-confirmation--destructive-actions)
|
|
20
|
+
12. [Empty & Error States](#12-empty--error-states)
|
|
21
|
+
13. [ASCII Art Elements](#13-ascii-art-elements)
|
|
22
|
+
14. [Theme Switching (Future)](#14-theme-switching-future)
|
|
23
|
+
15. [Quick-Start Checklist](#15-quick-start-checklist)
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 1. Design Philosophy
|
|
28
|
+
|
|
29
|
+
### Core Principles
|
|
30
|
+
|
|
31
|
+
| Principle | Description |
|
|
32
|
+
|-----------|-------------|
|
|
33
|
+
| **Guided, not guessed** | Every screen tells the user what it does and what to do next. No blank screens, no mystery options. |
|
|
34
|
+
| **Vertical rhythm** | Content flows top-to-bottom in a single column. The gutter line (`│`) creates visual continuity. |
|
|
35
|
+
| **Progressive disclosure** | Show only what's needed at each step. Wizards reveal one step at a time. |
|
|
36
|
+
| **Forgiving navigation** | Shift+Tab goes back. Esc returns to menu. Data is preserved when navigating backward. |
|
|
37
|
+
| **Config-driven** | Criteria, labels, teams, titles, levels — all configurable. The UI adapts to config, not the reverse. |
|
|
38
|
+
| **Minimal footprint** | No external UI libraries beyond Ink.js core. Every component is purpose-built and lightweight. |
|
|
39
|
+
|
|
40
|
+
### Interaction Model
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
User sees Heading → reads Instruction → scans CTAs → selects action → enters content area
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The user should **never** need to guess what a screen does or where they are in a workflow.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 2. Color System & Theme Architecture
|
|
51
|
+
|
|
52
|
+
### Semantic Color Roles
|
|
53
|
+
|
|
54
|
+
The color system is **semantic** — components reference roles, not hex values. This enables runtime theme switching.
|
|
55
|
+
|
|
56
|
+
| Role | Token | Purpose | Usage |
|
|
57
|
+
|------|-------|---------|-------|
|
|
58
|
+
| **brand** | `C.mauve` | Brand identity, active focus, cursor highlight | Banner, active step indicator, active select item (`❯`) |
|
|
59
|
+
| **heading** | `C.sapphire` | Screen titles, section headers | `<Text color={C.sapphire} bold>` |
|
|
60
|
+
| **success** | `C.green` | Completed steps, confirmed values, save success | Step indicator `◇`, welcome message, saved total |
|
|
61
|
+
| **warning** | `C.peach` | Warnings, caution states | Warning messages (rarely used) |
|
|
62
|
+
| **error** | `C.red` | Errors, destructive confirmations | Validation errors, delete confirmations |
|
|
63
|
+
| **highlight** | `C.yellow` | In-progress input, typing indicator | Currently active TextInput value |
|
|
64
|
+
| **accent** | `C.teal` | Secondary accent | Custom option indicator in multi-select |
|
|
65
|
+
| **muted** | `C.overlay1` | Gutter lines, hints, instructions, dim labels | `┌ │ └`, descriptions, navigation hints |
|
|
66
|
+
| **dimmed** | `C.overlay0` | Pending/inactive steps | Pending step indicator `○`, "No notes" text |
|
|
67
|
+
| **input-active** | `C.inputing` | Value currently being typed | TextInput active value |
|
|
68
|
+
| **input-confirmed** | `C.inputed` | Value already confirmed | Completed step summaries, confirmed field values |
|
|
69
|
+
| **label** | `C.label` | General text labels, content text | Field labels, employee names, note content |
|
|
70
|
+
|
|
71
|
+
### Built-in Theme Definitions
|
|
72
|
+
|
|
73
|
+
Four themes ship pre-installed. Each maps the same semantic roles to theme-specific hex values.
|
|
74
|
+
|
|
75
|
+
#### Catppuccin Mocha (Dark) — Default
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
export const catppuccinMocha = {
|
|
79
|
+
name: 'catppuccin-mocha',
|
|
80
|
+
type: 'dark',
|
|
81
|
+
colors: {
|
|
82
|
+
mauve: '#cba6f7', // Catppuccin Mocha Mauve
|
|
83
|
+
green: '#a6e3a1', // Catppuccin Mocha Green
|
|
84
|
+
yellow: '#f9e2af', // Catppuccin Mocha Yellow
|
|
85
|
+
sapphire: '#74c7ec', // Catppuccin Mocha Sapphire
|
|
86
|
+
teal: '#94e2d5', // Catppuccin Mocha Teal
|
|
87
|
+
red: '#f38ba8', // Catppuccin Mocha Red
|
|
88
|
+
peach: '#fab387', // Catppuccin Mocha Peach
|
|
89
|
+
overlay1: '#7f849c', // Catppuccin Mocha Overlay1
|
|
90
|
+
overlay0: '#6c7086', // Catppuccin Mocha Overlay0
|
|
91
|
+
inputing: '#f9e2af', // = yellow
|
|
92
|
+
inputed: '#a6e3a1', // = green
|
|
93
|
+
label: '#cdd6f4', // Catppuccin Mocha Text
|
|
94
|
+
},
|
|
95
|
+
} as const;
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### Catppuccin Latte (Light)
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
export const catppuccinLatte = {
|
|
102
|
+
name: 'catppuccin-latte',
|
|
103
|
+
type: 'light',
|
|
104
|
+
colors: {
|
|
105
|
+
mauve: '#8839ef', // Catppuccin Latte Mauve
|
|
106
|
+
green: '#40a02b', // Catppuccin Latte Green
|
|
107
|
+
yellow: '#df8e1d', // Catppuccin Latte Yellow
|
|
108
|
+
sapphire: '#209fb5', // Catppuccin Latte Sapphire
|
|
109
|
+
teal: '#179299', // Catppuccin Latte Teal
|
|
110
|
+
red: '#d20f39', // Catppuccin Latte Red
|
|
111
|
+
peach: '#fe640b', // Catppuccin Latte Peach
|
|
112
|
+
overlay1: '#8c8fa1', // Catppuccin Latte Overlay1
|
|
113
|
+
overlay0: '#9ca0b0', // Catppuccin Latte Overlay0
|
|
114
|
+
inputing: '#df8e1d', // = yellow
|
|
115
|
+
inputed: '#40a02b', // = green
|
|
116
|
+
label: '#4c4f69', // Catppuccin Latte Text
|
|
117
|
+
},
|
|
118
|
+
} as const;
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
#### Tokyo Night (Dark)
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
export const tokyoNightDark = {
|
|
125
|
+
name: 'tokyonight-dark',
|
|
126
|
+
type: 'dark',
|
|
127
|
+
colors: {
|
|
128
|
+
mauve: '#bb9af7', // Tokyo Night Purple
|
|
129
|
+
green: '#9ece6a', // Tokyo Night Green
|
|
130
|
+
yellow: '#e0af68', // Tokyo Night Yellow
|
|
131
|
+
sapphire: '#7aa2f7', // Tokyo Night Blue
|
|
132
|
+
teal: '#73daca', // Tokyo Night Teal
|
|
133
|
+
red: '#f7768e', // Tokyo Night Red
|
|
134
|
+
peach: '#ff9e64', // Tokyo Night Orange
|
|
135
|
+
overlay1: '#565f89', // Tokyo Night Comment
|
|
136
|
+
overlay0: '#414868', // Tokyo Night Dark3
|
|
137
|
+
inputing: '#e0af68', // = yellow
|
|
138
|
+
inputed: '#9ece6a', // = green
|
|
139
|
+
label: '#c0caf5', // Tokyo Night Foreground
|
|
140
|
+
},
|
|
141
|
+
} as const;
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
#### Tokyo Night Light
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
export const tokyoNightLight = {
|
|
148
|
+
name: 'tokyonight-light',
|
|
149
|
+
type: 'light',
|
|
150
|
+
colors: {
|
|
151
|
+
mauve: '#7847bd', // Tokyo Night Light Purple
|
|
152
|
+
green: '#485e30', // Tokyo Night Light Green
|
|
153
|
+
yellow: '#8f5e15', // Tokyo Night Light Yellow
|
|
154
|
+
sapphire: '#2e7de9', // Tokyo Night Light Blue
|
|
155
|
+
teal: '#387068', // Tokyo Night Light Teal
|
|
156
|
+
red: '#f52a65', // Tokyo Night Light Red
|
|
157
|
+
peach: '#b15c00', // Tokyo Night Light Orange
|
|
158
|
+
overlay1: '#9699a3', // Tokyo Night Light Comment
|
|
159
|
+
overlay0: '#b4b5b9', // Tokyo Night Light Dark3
|
|
160
|
+
inputing: '#8f5e15', // = yellow
|
|
161
|
+
inputed: '#485e30', // = green
|
|
162
|
+
label: '#3760bf', // Tokyo Night Light Foreground
|
|
163
|
+
},
|
|
164
|
+
} as const;
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Theme File Structure
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
src/lib/
|
|
171
|
+
├── theme.ts # Exports `C` — the active color tokens
|
|
172
|
+
└── themes/
|
|
173
|
+
├── index.ts # Theme registry, loader, switcher
|
|
174
|
+
├── catppuccin-mocha.ts
|
|
175
|
+
├── catppuccin-latte.ts
|
|
176
|
+
├── tokyonight-dark.ts
|
|
177
|
+
└── tokyonight-light.ts
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Theme Type Contract
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
export interface ThemeDefinition {
|
|
184
|
+
name: string;
|
|
185
|
+
type: 'dark' | 'light';
|
|
186
|
+
colors: {
|
|
187
|
+
mauve: string;
|
|
188
|
+
green: string;
|
|
189
|
+
yellow: string;
|
|
190
|
+
sapphire: string;
|
|
191
|
+
teal: string;
|
|
192
|
+
red: string;
|
|
193
|
+
peach: string;
|
|
194
|
+
overlay1: string;
|
|
195
|
+
overlay0: string;
|
|
196
|
+
inputing: string;
|
|
197
|
+
inputed: string;
|
|
198
|
+
label: string;
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Runtime Theme Switching
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
# Set theme via CLI command
|
|
207
|
+
cli-name theme catppuccin-mocha # default
|
|
208
|
+
cli-name theme catppuccin-latte
|
|
209
|
+
cli-name theme tokyonight-dark
|
|
210
|
+
cli-name theme tokyonight-light
|
|
211
|
+
|
|
212
|
+
# List available themes
|
|
213
|
+
cli-name theme --list
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**Implementation pattern:**
|
|
217
|
+
1. Theme preference stored in config file (e.g., `data/config.json` → `{ "theme": "catppuccin-mocha" }`)
|
|
218
|
+
2. On startup, `theme.ts` reads config → loads matching theme definition → exports `C` object
|
|
219
|
+
3. All components import `C` from `theme.ts` — never reference hex values directly
|
|
220
|
+
4. Theme switch updates config file → next render uses new colors
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## 3. Typography & Text Hierarchy
|
|
225
|
+
|
|
226
|
+
Terminal UI has no font sizes, so hierarchy is conveyed through **color**, **boldness**, and **position**.
|
|
227
|
+
|
|
228
|
+
### Text Levels
|
|
229
|
+
|
|
230
|
+
| Level | Style | Token | Example |
|
|
231
|
+
|-------|-------|-------|---------|
|
|
232
|
+
| **Banner** | Bold, brand color | `C.mauve` | ASCII art app name |
|
|
233
|
+
| **Screen Heading** | Bold, heading color | `C.sapphire bold` | `Main Menu`, `New Assessment` |
|
|
234
|
+
| **Screen Instruction** | Normal, muted | `C.overlay1` | `Follow each step to record an employee evaluation.` |
|
|
235
|
+
| **Section Header** | Bold, heading color | `C.sapphire bold` | `Technical (50%)` within compare |
|
|
236
|
+
| **Field Label** | Normal, muted | `C.overlay1` | `Name:`, `Team:`, `Scores:` |
|
|
237
|
+
| **Field Value** | Normal or bold, label color | `C.label` or `C.inputed` | Employee name, confirmed input |
|
|
238
|
+
| **Active Input** | Yellow/highlight | `C.inputing` | Text currently being typed |
|
|
239
|
+
| **Confirmed Input** | Green/success | `C.inputed` | Completed step summary |
|
|
240
|
+
| **Hint Text** | Normal, muted | `C.overlay1` | `Shift+Tab to go back`, `↑↓ move, enter select` |
|
|
241
|
+
| **Error Text** | Normal, error color | `C.red` | `Name is required` |
|
|
242
|
+
| **Dimmed Text** | Normal, dimmed | `C.overlay0` | Pending step labels, `No notes` |
|
|
243
|
+
|
|
244
|
+
### Text Rules
|
|
245
|
+
|
|
246
|
+
- **Headings** are always the first line of a screen, left-aligned, no gutter prefix
|
|
247
|
+
- **Instructions** appear directly below the heading, same alignment, muted color, no bold
|
|
248
|
+
- **Labels** use colon suffix: `Name: `, `Team: `, `Score: `
|
|
249
|
+
- **Values** follow labels on the same line, using `C.label` or `C.inputed`
|
|
250
|
+
- **Hints** appear below input areas, always `C.overlay1`
|
|
251
|
+
- **Never** use ALL CAPS for emphasis — use bold + color instead
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## 4. Layout System — The Gutter Pattern
|
|
256
|
+
|
|
257
|
+
The gutter creates a continuous vertical line that visually groups related content, similar to a card or container in graphical UIs.
|
|
258
|
+
|
|
259
|
+
### Gutter Characters
|
|
260
|
+
|
|
261
|
+
```
|
|
262
|
+
┌ ← Opening corner (start of content block)
|
|
263
|
+
│ content ← Gutter line with 2-space indent after pipe
|
|
264
|
+
│ ← Empty gutter line (spacer)
|
|
265
|
+
└ ← Closing corner (end of content block)
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### GutterLine Component
|
|
269
|
+
|
|
270
|
+
```tsx
|
|
271
|
+
// Every line inside a gutter block uses this component
|
|
272
|
+
export function GutterLine({ children }: { children?: React.ReactNode }) {
|
|
273
|
+
return (
|
|
274
|
+
<Box flexDirection="row">
|
|
275
|
+
<Text color={C.overlay1}>{"│ "}</Text>
|
|
276
|
+
{children}
|
|
277
|
+
</Box>
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**Rules:**
|
|
283
|
+
- Everything between `┌` and `└` uses `GutterLine` for content
|
|
284
|
+
- `GutterLine` with no children renders an empty spacer line (`│`)
|
|
285
|
+
- The gutter prefix is always `│` + 2 spaces, colored `C.overlay1`
|
|
286
|
+
- CTAs (GutteredSelect) render their own gutter prefix internally — do not wrap in GutterLine
|
|
287
|
+
- `┌` and `└` are standalone `<Text>` elements, not inside GutterLine
|
|
288
|
+
|
|
289
|
+
### Nesting Pattern
|
|
290
|
+
|
|
291
|
+
Gutter blocks do **not** nest. Each screen/view has exactly one gutter block.
|
|
292
|
+
|
|
293
|
+
```
|
|
294
|
+
Heading ← outside gutter
|
|
295
|
+
Instruction ← outside gutter
|
|
296
|
+
┌ ← gutter start
|
|
297
|
+
│ Welcome message ← GutterLine
|
|
298
|
+
│ Instruction line 1 ← GutterLine
|
|
299
|
+
│ ← GutterLine spacer
|
|
300
|
+
│ ❯ Option 1 ← GutteredSelect (internal gutter)
|
|
301
|
+
│ Option 2 ← GutteredSelect (internal gutter)
|
|
302
|
+
│ ↑↓ move, enter select ← GutteredSelect hint (internal gutter)
|
|
303
|
+
└ ← gutter end
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## 5. Screen Anatomy
|
|
309
|
+
|
|
310
|
+
Every screen follows this consistent structure:
|
|
311
|
+
|
|
312
|
+
```
|
|
313
|
+
┌─────────────────────────────────────────────────┐
|
|
314
|
+
│ Heading (C.sapphire, bold) │ ← 1. Screen title
|
|
315
|
+
│ Instruction (C.overlay1) │ ← 2. One-line description
|
|
316
|
+
│ ┌ │ ← 3. Gutter open
|
|
317
|
+
│ │ Context info (C.overlay1 labels) │ ← 4. Contextual data
|
|
318
|
+
│ │ │ ← 5. Spacer
|
|
319
|
+
│ │ ❯ Action 1 (GutteredSelect) │ ← 6. CTAs
|
|
320
|
+
│ │ Action 2 │
|
|
321
|
+
│ │ ↑↓ move, enter select │
|
|
322
|
+
│ └ │ ← 7. Gutter close
|
|
323
|
+
└─────────────────────────────────────────────────┘
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Screen Types
|
|
327
|
+
|
|
328
|
+
#### 1. Menu Screen (Main Menu, Sub-menus)
|
|
329
|
+
|
|
330
|
+
```
|
|
331
|
+
Heading
|
|
332
|
+
┌
|
|
333
|
+
│ Welcome / context info
|
|
334
|
+
│
|
|
335
|
+
│ ❯ CTA options...
|
|
336
|
+
│ ↑↓ move, enter select
|
|
337
|
+
└
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
- Welcome message or context above CTAs inside gutter
|
|
341
|
+
- Always includes a "Back" option as last CTA
|
|
342
|
+
|
|
343
|
+
#### 2. List Screen (View Employees, Search Results)
|
|
344
|
+
|
|
345
|
+
```
|
|
346
|
+
Heading
|
|
347
|
+
Instruction
|
|
348
|
+
┌
|
|
349
|
+
│ Count info (e.g., "5 employees")
|
|
350
|
+
│ ❯ Item 1 — description
|
|
351
|
+
│ Item 2 — description
|
|
352
|
+
│ Back to Menu
|
|
353
|
+
│ ↑↓ move, enter select
|
|
354
|
+
└
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
- Count summary before items
|
|
358
|
+
- Scrollable with `maxVisible` prop
|
|
359
|
+
- Back option at bottom of list
|
|
360
|
+
|
|
361
|
+
#### 3. Detail Screen (Employee Detail)
|
|
362
|
+
|
|
363
|
+
```
|
|
364
|
+
Heading
|
|
365
|
+
┌
|
|
366
|
+
│ Name (bold)
|
|
367
|
+
│ Label: Value
|
|
368
|
+
│ Label: Value
|
|
369
|
+
│
|
|
370
|
+
│ Section data...
|
|
371
|
+
│
|
|
372
|
+
│ ❯ Action options...
|
|
373
|
+
│ ↑↓ move, enter select
|
|
374
|
+
└
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
- Read-only info block at top
|
|
378
|
+
- Actions at bottom
|
|
379
|
+
|
|
380
|
+
#### 4. Wizard Screen (New Assessment)
|
|
381
|
+
|
|
382
|
+
```
|
|
383
|
+
Heading
|
|
384
|
+
Instruction
|
|
385
|
+
┌
|
|
386
|
+
◇ Step 1 (completed)
|
|
387
|
+
│ Summary of step 1
|
|
388
|
+
│
|
|
389
|
+
◆ Step 2 (active)
|
|
390
|
+
│ [Active step content — forms, selects]
|
|
391
|
+
│
|
|
392
|
+
○ Step 3 (pending)
|
|
393
|
+
│
|
|
394
|
+
○ Step 4 (pending)
|
|
395
|
+
└
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
- Uses Timeline component with StepIndicator
|
|
399
|
+
- One step active at a time
|
|
400
|
+
- Completed steps show summaries
|
|
401
|
+
|
|
402
|
+
#### 5. Confirmation Screen
|
|
403
|
+
|
|
404
|
+
```
|
|
405
|
+
Heading
|
|
406
|
+
┌
|
|
407
|
+
│ Warning/summary text
|
|
408
|
+
│ Detail text
|
|
409
|
+
│ ❯ Cancel (safe option first)
|
|
410
|
+
│ Destructive action
|
|
411
|
+
│ ↑↓ move, enter select
|
|
412
|
+
└
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
- Safe option (Cancel) always listed first
|
|
416
|
+
- Destructive option uses red text or clear labeling
|
|
417
|
+
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
## 6. Navigation Patterns
|
|
421
|
+
|
|
422
|
+
### Global Navigation
|
|
423
|
+
|
|
424
|
+
| Key | Context | Action |
|
|
425
|
+
|-----|---------|--------|
|
|
426
|
+
| `Esc` | Any screen except menu | Return to Main Menu |
|
|
427
|
+
| `↑` / `↓` | GutteredSelect active | Move selection cursor |
|
|
428
|
+
| `Enter` | GutteredSelect active | Confirm selection |
|
|
429
|
+
| `Space` | GutteredMultiSelect active | Toggle item |
|
|
430
|
+
| `Shift+Tab` | Wizard step / Form field | Go back to previous field/step |
|
|
431
|
+
|
|
432
|
+
### Back Navigation Strategy
|
|
433
|
+
|
|
434
|
+
Every view must have a **visible** way to go back:
|
|
435
|
+
|
|
436
|
+
1. **Wizard steps**: Shift+Tab navigates to previous step/field. Hint text shows `Shift+Tab to go back`.
|
|
437
|
+
2. **List/Menu screens**: Last CTA option is always `Back to Menu` / `Back to List` / `Back to Detail`.
|
|
438
|
+
3. **Global**: Esc returns to Main Menu from any screen.
|
|
439
|
+
|
|
440
|
+
### Shift+Tab Implementation
|
|
441
|
+
|
|
442
|
+
```tsx
|
|
443
|
+
useInput((_input, key) => {
|
|
444
|
+
if (!(key.shift && key.tab)) return;
|
|
445
|
+
// Navigate to previous phase, preserving form data
|
|
446
|
+
switch (phase) {
|
|
447
|
+
case 'field-2': setPhase('field-1'); break;
|
|
448
|
+
case 'field-3': setPhase('field-2'); break;
|
|
449
|
+
// ...
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
**Rules:**
|
|
455
|
+
- Form data is **preserved** when going back — never clear on Shift+Tab
|
|
456
|
+
- Clear only the error state when going back
|
|
457
|
+
- Sub-phases (e.g., `team-custom`) go back to their parent phase (`team`)
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
## 7. Wizard & Timeline Pattern
|
|
462
|
+
|
|
463
|
+
### Timeline Component
|
|
464
|
+
|
|
465
|
+
The Timeline renders a vertical wizard with step indicators and a continuous gutter line.
|
|
466
|
+
|
|
467
|
+
```
|
|
468
|
+
┌
|
|
469
|
+
◇ Employee ← completed (green diamond outline)
|
|
470
|
+
│ John Doe — Senior ← summary (green text)
|
|
471
|
+
│
|
|
472
|
+
◆ Interviewer ← active (animated purple diamond)
|
|
473
|
+
│ [form content] ← active step content
|
|
474
|
+
│
|
|
475
|
+
○ Scores ← pending (dim circle)
|
|
476
|
+
│
|
|
477
|
+
○ Notes ← pending
|
|
478
|
+
└
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### Step Indicators
|
|
482
|
+
|
|
483
|
+
| Status | Symbol | Animation | Color |
|
|
484
|
+
|--------|--------|-----------|-------|
|
|
485
|
+
| **Completed** | `◇` | None | `C.green` |
|
|
486
|
+
| **Active** | `◆` → `◈` → `◇` → `◈` | 350ms cycle | `C.mauve` (bold) |
|
|
487
|
+
| **Pending** | `○` | None | `C.overlay0` |
|
|
488
|
+
|
|
489
|
+
### Step State Machine
|
|
490
|
+
|
|
491
|
+
```
|
|
492
|
+
pending → active → completed
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
- Only one step is active at a time
|
|
496
|
+
- `currentStep` index controls which step is active
|
|
497
|
+
- Steps before `currentStep` are completed; steps after are pending
|
|
498
|
+
- Each step component calls `onComplete(data)` which advances `currentStep`
|
|
499
|
+
|
|
500
|
+
### Summary Display
|
|
501
|
+
|
|
502
|
+
When a step is completed, its summary appears below the indicator:
|
|
503
|
+
|
|
504
|
+
```
|
|
505
|
+
◇ Scores
|
|
506
|
+
│ Tech: 4 | Eff: 3 | Adapt: 5 = 3.90
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
Summary is computed by a `getSummary(stepIndex)` function in the parent screen.
|
|
510
|
+
|
|
511
|
+
---
|
|
512
|
+
|
|
513
|
+
## 8. Form Patterns
|
|
514
|
+
|
|
515
|
+
### TextInput
|
|
516
|
+
|
|
517
|
+
```
|
|
518
|
+
│ Field Label: ← GutterLine with bold text
|
|
519
|
+
│ [input value] ← GutterLine with TextInput
|
|
520
|
+
│ Error message ← GutterLine with C.red (conditional)
|
|
521
|
+
│ Hint text ← GutterLine with C.overlay1
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
**Props pattern:**
|
|
525
|
+
```tsx
|
|
526
|
+
<GutterLine><Text bold>Employee Name:</Text></GutterLine>
|
|
527
|
+
<GutterLine>
|
|
528
|
+
<TextInput
|
|
529
|
+
placeholder="Full name (required)"
|
|
530
|
+
defaultValue={form.name}
|
|
531
|
+
onSubmit={(val) => { /* validate and advance */ }}
|
|
532
|
+
/>
|
|
533
|
+
</GutterLine>
|
|
534
|
+
{error && <GutterLine><Text color={C.red}>{error}</Text></GutterLine>}
|
|
535
|
+
<GutterLine><Text color={C.overlay1}>Shift+Tab to go back</Text></GutterLine>
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
### GutteredSelect (Single Select)
|
|
539
|
+
|
|
540
|
+
```
|
|
541
|
+
│ ❯ Option 1 — description ← active item (C.mauve, bold)
|
|
542
|
+
│ Option 2 — description ← inactive item
|
|
543
|
+
│ Option 3 — description
|
|
544
|
+
│ ↑↓ move, enter select ← hint
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
**Every option should have a `description` prop** for context.
|
|
548
|
+
|
|
549
|
+
**Scrolling**: When options exceed `maxVisible` (default 5), shows `↑ more` / `↓ more` indicators.
|
|
550
|
+
|
|
551
|
+
### GutteredMultiSelect
|
|
552
|
+
|
|
553
|
+
```
|
|
554
|
+
│ ❯ ● Item 1 — description ← selected (C.green dot)
|
|
555
|
+
│ ○ Item 2 — description ← unselected (C.overlay0 dot)
|
|
556
|
+
│ ● Item 3 — description
|
|
557
|
+
│ ↑↓ move, space toggle, enter confirm
|
|
558
|
+
│ Selected: Item 1, Item 3
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
**Features:**
|
|
562
|
+
- `●` for selected, `○` for unselected
|
|
563
|
+
- Optional `allowCustom` for "Other (type custom)" option
|
|
564
|
+
- Selected items shown at bottom as comma-separated list
|
|
565
|
+
|
|
566
|
+
### Validation Pattern
|
|
567
|
+
|
|
568
|
+
```tsx
|
|
569
|
+
onSubmit={(val) => {
|
|
570
|
+
if (!validateRequired(val)) {
|
|
571
|
+
setError('Name is required');
|
|
572
|
+
return; // Don't advance
|
|
573
|
+
}
|
|
574
|
+
setError('');
|
|
575
|
+
setForm({ ...form, name: val });
|
|
576
|
+
setPhase('next-field');
|
|
577
|
+
}}
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
- Validate on submit, not on change
|
|
581
|
+
- Show error below input in `C.red`
|
|
582
|
+
- Clear error on successful validation or Shift+Tab
|
|
583
|
+
|
|
584
|
+
### Optional Fields
|
|
585
|
+
|
|
586
|
+
```
|
|
587
|
+
│ Current Level:
|
|
588
|
+
│ [input]
|
|
589
|
+
│ Optional — press enter to skip · Shift+Tab to go back
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
- Hint explicitly states "Optional — press enter to skip"
|
|
593
|
+
- Empty submit = `null` value
|
|
594
|
+
|
|
595
|
+
---
|
|
596
|
+
|
|
597
|
+
## 9. Component Library
|
|
598
|
+
|
|
599
|
+
### Banner
|
|
600
|
+
|
|
601
|
+
ASCII art header at the top of the application. Displayed once, always visible.
|
|
602
|
+
|
|
603
|
+
```
|
|
604
|
+
█▀▀ █▀█ █▀▄▀█ █▀█ █▀▀ ▀█▀ █▀▀ █▀█ █▀▀ █▄█
|
|
605
|
+
█ █ █ █ ▀ █ █▀▀ █▀▀ █ █▀▀ █ █ █ ░█░
|
|
606
|
+
▀▀▀ ▀▀▀ ▀ ▀ ▀ ▀▀▀ ▀ ▀▀▀ ▀ ▀ ▀▀▀ ░▀░
|
|
607
|
+
App Subtitle
|
|
608
|
+
────────────────────────────────────────────────
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
- Banner text: `C.mauve`
|
|
612
|
+
- Subtitle: default text color
|
|
613
|
+
- Separator: `─` repeated, `C.overlay1`
|
|
614
|
+
- `marginBottom={0}` — spacing handled by parent
|
|
615
|
+
|
|
616
|
+
### Timeline
|
|
617
|
+
|
|
618
|
+
Vertical wizard container. Wraps step indicators + active step content.
|
|
619
|
+
|
|
620
|
+
```tsx
|
|
621
|
+
<Timeline steps={timelineSteps}>
|
|
622
|
+
{renderActiveStep()}
|
|
623
|
+
</Timeline>
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
- `steps: TimelineStep[]` — name, status, summary
|
|
627
|
+
- `children` — rendered inline under the active step
|
|
628
|
+
- Handles `┌` / `└` corners and `│` spacers between steps
|
|
629
|
+
|
|
630
|
+
### GutterLine
|
|
631
|
+
|
|
632
|
+
Single line within a gutter block.
|
|
633
|
+
|
|
634
|
+
```tsx
|
|
635
|
+
<GutterLine>
|
|
636
|
+
<Text color={C.overlay1}>Label: </Text>
|
|
637
|
+
<Text color={C.label}>Value</Text>
|
|
638
|
+
</GutterLine>
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
- Prefix: `│` + 2 spaces (`C.overlay1`)
|
|
642
|
+
- Empty `<GutterLine />` = spacer line
|
|
643
|
+
|
|
644
|
+
### GutteredSelect
|
|
645
|
+
|
|
646
|
+
Custom single-select with gutter integration.
|
|
647
|
+
|
|
648
|
+
```tsx
|
|
649
|
+
<GutteredSelect
|
|
650
|
+
options={[
|
|
651
|
+
{ label: 'Option 1', value: 'opt1', description: 'What this does' },
|
|
652
|
+
{ label: 'Back', value: 'back', description: 'Return to previous screen' },
|
|
653
|
+
]}
|
|
654
|
+
onChange={(val) => handleSelect(val)}
|
|
655
|
+
maxVisible={5}
|
|
656
|
+
/>
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
- Renders own gutter prefix
|
|
660
|
+
- Active item: `❯` in `C.mauve`
|
|
661
|
+
- Scroll indicators when items exceed `maxVisible`
|
|
662
|
+
- ANSI input normalization for deterministic key handling
|
|
663
|
+
|
|
664
|
+
### GutteredMultiSelect
|
|
665
|
+
|
|
666
|
+
Multi-select variant with space-to-toggle and enter-to-confirm.
|
|
667
|
+
|
|
668
|
+
```tsx
|
|
669
|
+
<GutteredMultiSelect
|
|
670
|
+
options={options}
|
|
671
|
+
onSubmit={(selectedIds) => handleSelection(selectedIds)}
|
|
672
|
+
allowCustom={true}
|
|
673
|
+
customPlaceholder="Type custom values"
|
|
674
|
+
/>
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
### StepIndicator
|
|
678
|
+
|
|
679
|
+
Animated step status indicator for timeline.
|
|
680
|
+
|
|
681
|
+
```tsx
|
|
682
|
+
<StepIndicator status="active" label="Scores" />
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
- `completed`: `◇` green + label in `C.label`
|
|
686
|
+
- `active`: animated `◆◈◇◈` at 350ms in `C.mauve` + bold label
|
|
687
|
+
- `pending`: `○` in `C.overlay0` + dim label
|
|
688
|
+
|
|
689
|
+
---
|
|
690
|
+
|
|
691
|
+
## 10. Welcome & Instruction Pattern
|
|
692
|
+
|
|
693
|
+
### Main Menu Welcome
|
|
694
|
+
|
|
695
|
+
Inside the gutter block, above CTAs:
|
|
696
|
+
|
|
697
|
+
```
|
|
698
|
+
Main Menu
|
|
699
|
+
┌
|
|
700
|
+
│ Welcome to [App Name]! ← C.green, bold, GutterLine
|
|
701
|
+
│ First line of description ← C.overlay1, GutterLine
|
|
702
|
+
│ Second line of description ← C.overlay1, GutterLine
|
|
703
|
+
│ ← GutterLine spacer
|
|
704
|
+
│ ❯ First action ← CTAs start here
|
|
705
|
+
│ Second action
|
|
706
|
+
└
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
**Rules:**
|
|
710
|
+
- Welcome line uses `C.green` bold
|
|
711
|
+
- Description lines use `C.overlay1`
|
|
712
|
+
- One empty GutterLine spacer between description and CTAs
|
|
713
|
+
- Welcome and description are **inside** the gutter, same indent as CTAs
|
|
714
|
+
|
|
715
|
+
### Per-Screen Instructions
|
|
716
|
+
|
|
717
|
+
Below the heading, outside the gutter, left-aligned:
|
|
718
|
+
|
|
719
|
+
```
|
|
720
|
+
Screen Heading ← C.sapphire, bold
|
|
721
|
+
One-line instruction text ← C.overlay1, <Text>
|
|
722
|
+
┌
|
|
723
|
+
│ Content...
|
|
724
|
+
└
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
**Rules:**
|
|
728
|
+
- Instruction is a plain `<Text color={C.overlay1}>` — no GutterLine, no bold
|
|
729
|
+
- Aligns left with the heading (no indent)
|
|
730
|
+
- One line only — concise description of what the screen does
|
|
731
|
+
- Not all screens need instructions — use for primary entry screens
|
|
732
|
+
|
|
733
|
+
**Example instructions:**
|
|
734
|
+
|
|
735
|
+
| Screen | Instruction |
|
|
736
|
+
|--------|-------------|
|
|
737
|
+
| New Assessment | Follow each step to record an employee evaluation. |
|
|
738
|
+
| Edit Assessment | Update scores, notes, or assessed levels for this assessment. |
|
|
739
|
+
| View Employees | Browse the employee list to view details and manage assessments. |
|
|
740
|
+
| Search & Filter | Find employees by name, team, title, or level. |
|
|
741
|
+
| Compare | Select two or more employees to compare their assessment results side by side. |
|
|
742
|
+
| Export CSV | Export assessment data as CSV for reporting and analysis. |
|
|
743
|
+
| Edit Employee | Update this employee's information. |
|
|
744
|
+
|
|
745
|
+
---
|
|
746
|
+
|
|
747
|
+
## 11. Confirmation & Destructive Actions
|
|
748
|
+
|
|
749
|
+
### Confirmation Dialog Pattern
|
|
750
|
+
|
|
751
|
+
```
|
|
752
|
+
Delete Assessment ← C.sapphire heading
|
|
753
|
+
┌
|
|
754
|
+
│ Delete assessment by John (2025-01-15)? ← C.red warning text
|
|
755
|
+
│ This cannot be undone. ← C.overlay1 detail
|
|
756
|
+
│ ❯ Cancel ← Safe option FIRST
|
|
757
|
+
│ Delete ← Destructive option
|
|
758
|
+
└
|
|
759
|
+
```
|
|
760
|
+
|
|
761
|
+
### Rules
|
|
762
|
+
|
|
763
|
+
1. **Cancel/safe option is always first** in the CTA list
|
|
764
|
+
2. **Destructive text uses `C.red`** for the warning message
|
|
765
|
+
3. **"This cannot be undone."** standard disclaimer in `C.overlay1`
|
|
766
|
+
4. **Never auto-select the destructive option** — cursor starts on Cancel
|
|
767
|
+
5. **Cascade warnings** — if deleting an employee also deletes N assessments, state it explicitly:
|
|
768
|
+
```
|
|
769
|
+
│ Delete John Doe and all 3 assessments?
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
### Description Props
|
|
773
|
+
|
|
774
|
+
Every CTA option includes a `description` for clarity:
|
|
775
|
+
|
|
776
|
+
```tsx
|
|
777
|
+
{ label: 'Cancel', value: 'cancel', description: 'Keep assessment and go back' }
|
|
778
|
+
{ label: 'Delete', value: 'delete', description: 'Permanently remove this assessment' }
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
---
|
|
782
|
+
|
|
783
|
+
## 12. Empty & Error States
|
|
784
|
+
|
|
785
|
+
### Empty State
|
|
786
|
+
|
|
787
|
+
When a list or view has no data:
|
|
788
|
+
|
|
789
|
+
```
|
|
790
|
+
View Employees
|
|
791
|
+
┌
|
|
792
|
+
│ No employees found ← C.overlay1
|
|
793
|
+
│ ❯ Create New Assessment ← Constructive action
|
|
794
|
+
│ Back to Menu
|
|
795
|
+
└
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
**Rules:**
|
|
799
|
+
- Show a clear message about what's empty
|
|
800
|
+
- Offer a constructive action (create something)
|
|
801
|
+
- Always include Back option
|
|
802
|
+
|
|
803
|
+
### Error State
|
|
804
|
+
|
|
805
|
+
```
|
|
806
|
+
│ Error: Failed to load employees: database locked ← C.red
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
or inline field error:
|
|
810
|
+
|
|
811
|
+
```
|
|
812
|
+
│ Employee Name:
|
|
813
|
+
│ [input]
|
|
814
|
+
│ Name is required ← C.red
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
**Rules:**
|
|
818
|
+
- Errors use `C.red`
|
|
819
|
+
- Field errors appear directly below the input
|
|
820
|
+
- Screen-level errors appear as GutterLine inside the gutter
|
|
821
|
+
- Never crash — always show error and offer navigation back
|
|
822
|
+
|
|
823
|
+
### Loading State
|
|
824
|
+
|
|
825
|
+
```
|
|
826
|
+
Loading... ← C.overlay1
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
- Shown during config/DB initialization
|
|
830
|
+
- Minimal — just text, no spinner needed for fast loads
|
|
831
|
+
- Use `ink-spinner` for longer async operations
|
|
832
|
+
|
|
833
|
+
---
|
|
834
|
+
|
|
835
|
+
## 13. ASCII Art Elements
|
|
836
|
+
|
|
837
|
+
### Score Bars (Compare Screen)
|
|
838
|
+
|
|
839
|
+
```
|
|
840
|
+
│ John Doe ████████████████░░░░ 4.00
|
|
841
|
+
│ Jane Smith ████████████░░░░░░░░ 3.00
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
```typescript
|
|
845
|
+
const BAR_WIDTH = 20;
|
|
846
|
+
function scoreBar(score: number | null, max: number = 5): string {
|
|
847
|
+
if (score === null) return '░'.repeat(BAR_WIDTH);
|
|
848
|
+
const filled = Math.round((score / max) * BAR_WIDTH);
|
|
849
|
+
return '█'.repeat(filled) + '░'.repeat(BAR_WIDTH - filled);
|
|
850
|
+
}
|
|
851
|
+
```
|
|
852
|
+
|
|
853
|
+
- Filled: `█` in `C.mauve`
|
|
854
|
+
- Empty: `░`
|
|
855
|
+
- Score value: `C.inputed` bold
|
|
856
|
+
- Width: 20 characters
|
|
857
|
+
|
|
858
|
+
### Separators
|
|
859
|
+
|
|
860
|
+
```
|
|
861
|
+
────────────────────────────────────────────────
|
|
862
|
+
```
|
|
863
|
+
|
|
864
|
+
- `─` repeated, colored `C.overlay1`
|
|
865
|
+
- Used after Banner subtitle
|
|
866
|
+
|
|
867
|
+
### Ranking Indicators
|
|
868
|
+
|
|
869
|
+
```
|
|
870
|
+
│ John Doe 4.50 #1 ← C.green for rank 1
|
|
871
|
+
│ Jane Smith 3.80 #2 ← C.overlay1 for others
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
- Top rank: `C.green` with `★` suffix
|
|
875
|
+
- Other ranks: `C.overlay1`
|
|
876
|
+
|
|
877
|
+
### Gutter Corners & Lines
|
|
878
|
+
|
|
879
|
+
| Character | Purpose | Color |
|
|
880
|
+
|-----------|---------|-------|
|
|
881
|
+
| `┌` | Block open | `C.overlay1` |
|
|
882
|
+
| `│` | Vertical gutter | `C.overlay1` |
|
|
883
|
+
| `└` | Block close | `C.overlay1` |
|
|
884
|
+
| `─` | Horizontal separator | `C.overlay1` |
|
|
885
|
+
|
|
886
|
+
### Selection Indicators
|
|
887
|
+
|
|
888
|
+
| Character | Meaning | Color |
|
|
889
|
+
|-----------|---------|-------|
|
|
890
|
+
| `❯` | Active cursor | `C.mauve` bold |
|
|
891
|
+
| `●` | Selected (multi) | `C.green` |
|
|
892
|
+
| `○` | Unselected (multi) | `C.overlay0` |
|
|
893
|
+
| `◇` | Completed step | `C.green` |
|
|
894
|
+
| `◆` | Active step | `C.mauve` bold |
|
|
895
|
+
| `✎` | Custom input option | default |
|
|
896
|
+
|
|
897
|
+
---
|
|
898
|
+
|
|
899
|
+
## 14. Theme Switching (Future)
|
|
900
|
+
|
|
901
|
+
### CLI Command
|
|
902
|
+
|
|
903
|
+
```bash
|
|
904
|
+
# Switch theme
|
|
905
|
+
cli-name theme tokyonight-dark
|
|
906
|
+
|
|
907
|
+
# List available themes
|
|
908
|
+
cli-name theme --list
|
|
909
|
+
# Output:
|
|
910
|
+
# catppuccin-mocha (dark) ← active
|
|
911
|
+
# catppuccin-latte (light)
|
|
912
|
+
# tokyonight-dark (dark)
|
|
913
|
+
# tokyonight-light (light)
|
|
914
|
+
```
|
|
915
|
+
|
|
916
|
+
### Implementation Architecture
|
|
917
|
+
|
|
918
|
+
```
|
|
919
|
+
src/lib/
|
|
920
|
+
├── theme.ts # Reads config, exports active `C`
|
|
921
|
+
└── themes/
|
|
922
|
+
├── index.ts # Registry + loader
|
|
923
|
+
├── catppuccin-mocha.ts # ThemeDefinition
|
|
924
|
+
├── catppuccin-latte.ts # ThemeDefinition
|
|
925
|
+
├── tokyonight-dark.ts # ThemeDefinition
|
|
926
|
+
└── tokyonight-light.ts # ThemeDefinition
|
|
927
|
+
|
|
928
|
+
data/
|
|
929
|
+
└── config.json # { "theme": "catppuccin-mocha" }
|
|
930
|
+
```
|
|
931
|
+
|
|
932
|
+
### theme.ts Pattern
|
|
933
|
+
|
|
934
|
+
```typescript
|
|
935
|
+
import { loadThemePreference } from './themes/index.js';
|
|
936
|
+
|
|
937
|
+
const theme = loadThemePreference(); // reads config.json, falls back to default
|
|
938
|
+
|
|
939
|
+
export const C = theme.colors;
|
|
940
|
+
```
|
|
941
|
+
|
|
942
|
+
### themes/index.ts Pattern
|
|
943
|
+
|
|
944
|
+
```typescript
|
|
945
|
+
import { catppuccinMocha } from './catppuccin-mocha.js';
|
|
946
|
+
import { catppuccinLatte } from './catppuccin-latte.js';
|
|
947
|
+
import { tokyoNightDark } from './tokyonight-dark.js';
|
|
948
|
+
import { tokyoNightLight } from './tokyonight-light.js';
|
|
949
|
+
import type { ThemeDefinition } from '../types.js';
|
|
950
|
+
|
|
951
|
+
const THEMES: Record<string, ThemeDefinition> = {
|
|
952
|
+
'catppuccin-mocha': catppuccinMocha,
|
|
953
|
+
'catppuccin-latte': catppuccinLatte,
|
|
954
|
+
'tokyonight-dark': tokyoNightDark,
|
|
955
|
+
'tokyonight-light': tokyoNightLight,
|
|
956
|
+
};
|
|
957
|
+
|
|
958
|
+
const DEFAULT_THEME = 'catppuccin-mocha';
|
|
959
|
+
|
|
960
|
+
export function loadThemePreference(): ThemeDefinition {
|
|
961
|
+
// Read from config file, fallback to default
|
|
962
|
+
try {
|
|
963
|
+
const config = JSON.parse(readFileSync('data/config.json', 'utf-8'));
|
|
964
|
+
return THEMES[config.theme] ?? THEMES[DEFAULT_THEME]!;
|
|
965
|
+
} catch {
|
|
966
|
+
return THEMES[DEFAULT_THEME]!;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
export function setTheme(themeName: string): boolean {
|
|
971
|
+
if (!THEMES[themeName]) return false;
|
|
972
|
+
// Write to config.json
|
|
973
|
+
writeConfigTheme(themeName);
|
|
974
|
+
return true;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
export function listThemes(): ThemeDefinition[] {
|
|
978
|
+
return Object.values(THEMES);
|
|
979
|
+
}
|
|
980
|
+
```
|
|
981
|
+
|
|
982
|
+
### Adding a Custom Theme
|
|
983
|
+
|
|
984
|
+
To add a new theme:
|
|
985
|
+
|
|
986
|
+
1. Create `src/lib/themes/my-theme.ts` implementing `ThemeDefinition`
|
|
987
|
+
2. Register it in `themes/index.ts` THEMES record
|
|
988
|
+
3. Rebuild — the theme is now available via `cli-name theme my-theme`
|
|
989
|
+
|
|
990
|
+
---
|
|
991
|
+
|
|
992
|
+
## 15. Quick-Start Checklist
|
|
993
|
+
|
|
994
|
+
When creating a new CLI app with this design system:
|
|
995
|
+
|
|
996
|
+
### Setup
|
|
997
|
+
|
|
998
|
+
- [ ] Initialize project: React 19 + Ink.js 6 + TypeScript (ES2022, Node16, `react-jsx`)
|
|
999
|
+
- [ ] Install: `react`, `ink`, `@inkjs/ui`, `ink-spinner`, `better-sqlite3`, `nanoid`
|
|
1000
|
+
- [ ] Create `src/lib/theme.ts` with semantic color tokens
|
|
1001
|
+
- [ ] Copy theme definitions for all 4 built-in themes
|
|
1002
|
+
- [ ] Create GutterLine, GutteredSelect, GutteredMultiSelect, StepIndicator, Timeline, Banner components
|
|
1003
|
+
|
|
1004
|
+
### Screen Scaffolding
|
|
1005
|
+
|
|
1006
|
+
- [ ] Entry point: `cli.tsx` → renders `<App />` via Ink's `render()`
|
|
1007
|
+
- [ ] App shell: Banner + screen router with `useState<Screen>('menu')`
|
|
1008
|
+
- [ ] Global Esc handler: `useInput` → return to menu
|
|
1009
|
+
- [ ] Main Menu with welcome message inside gutter
|
|
1010
|
+
- [ ] Each screen: Heading → Instruction → Gutter block → Content → CTAs
|
|
1011
|
+
|
|
1012
|
+
### Interaction
|
|
1013
|
+
|
|
1014
|
+
- [ ] Every GutteredSelect option has `label` + `value` + `description`
|
|
1015
|
+
- [ ] Every screen has a visible Back option
|
|
1016
|
+
- [ ] Shift+Tab navigates backward in all wizard steps and form fields
|
|
1017
|
+
- [ ] Form data preserved on backward navigation
|
|
1018
|
+
- [ ] Validation on submit, errors in `C.red`, cleared on re-attempt or Shift+Tab
|
|
1019
|
+
- [ ] Cancel before Delete in confirmation dialogs
|
|
1020
|
+
|
|
1021
|
+
### Polish
|
|
1022
|
+
|
|
1023
|
+
- [ ] Empty states with constructive actions
|
|
1024
|
+
- [ ] Error states with `C.red` and navigation back
|
|
1025
|
+
- [ ] Scroll indicators (`↑ more` / `↓ more`) for long lists
|
|
1026
|
+
- [ ] Navigation hints on every interactive element (`↑↓ move, enter select`, `Shift+Tab to go back`)
|
|
1027
|
+
- [ ] ASCII banner with brand identity
|
|
1028
|
+
|
|
1029
|
+
---
|
|
1030
|
+
|
|
1031
|
+
## Appendix: Reference Implementation
|
|
1032
|
+
|
|
1033
|
+
This guideline was extracted from the **Team Competency Assessment Tool** — a production CLI app built with this exact design system. Source code structure:
|
|
1034
|
+
|
|
1035
|
+
```
|
|
1036
|
+
src/
|
|
1037
|
+
├── cli.tsx # Entry point
|
|
1038
|
+
├── app.tsx # App shell, menu, screen router
|
|
1039
|
+
├── components/
|
|
1040
|
+
│ ├── banner.tsx # ASCII art header
|
|
1041
|
+
│ ├── timeline.tsx # Vertical wizard with step indicators
|
|
1042
|
+
│ ├── step-indicator.tsx # Animated ◆/◇/○ indicators
|
|
1043
|
+
│ ├── gutter-line.tsx # │ prefix component
|
|
1044
|
+
│ └── guttered-select.tsx # Select + MultiSelect with gutter
|
|
1045
|
+
├── screens/
|
|
1046
|
+
│ ├── new-assessment.tsx # 6-step wizard
|
|
1047
|
+
│ ├── edit-assessment.tsx # 4-step wizard
|
|
1048
|
+
│ ├── view-employees.tsx # List → Detail → Actions
|
|
1049
|
+
│ ├── search.tsx # Filter → Results → Detail
|
|
1050
|
+
│ ├── compare.tsx # MultiSelect → Score bars
|
|
1051
|
+
│ ├── export.tsx # Menu → Result
|
|
1052
|
+
│ ├── edit-employee.tsx # Multi-field form
|
|
1053
|
+
│ └── steps/ # Wizard step components
|
|
1054
|
+
│ ├── employee-step.tsx # 5-phase: Name→Team→Title→Level→Manager
|
|
1055
|
+
│ ├── interviewer-step.tsx # 4-phase: Name→Team→Title→Level
|
|
1056
|
+
│ ├── scores-step.tsx # Score input per criterion
|
|
1057
|
+
│ ├── notes-step.tsx # Multi-field text input
|
|
1058
|
+
│ ├── assessed-levels-step.tsx# Add/remove skill entries
|
|
1059
|
+
│ └── confirm-step.tsx # Review + save
|
|
1060
|
+
└── lib/
|
|
1061
|
+
├── theme.ts # Color tokens (C.mauve, C.green, etc.)
|
|
1062
|
+
├── types.ts # All TypeScript types
|
|
1063
|
+
├── config.ts # JSON config loader
|
|
1064
|
+
├── db.ts # SQLite CRUD
|
|
1065
|
+
├── csv.ts # CSV export
|
|
1066
|
+
├── employee-config.ts # Teams, titles, levels
|
|
1067
|
+
├── validation.ts # Score validation
|
|
1068
|
+
└── input/
|
|
1069
|
+
└── normalize-keys.ts # ANSI input normalization
|
|
1070
|
+
```
|