@atlashub/smartstack-cli 3.23.0 → 3.25.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/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp-entry.mjs +96 -24
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -1
- package/templates/mcp-scaffolding/component.tsx.hbs +21 -1
- package/templates/skills/apex/references/smartstack-api.md +174 -5
- package/templates/skills/apex/references/smartstack-frontend.md +1101 -0
- package/templates/skills/apex/references/smartstack-layers.md +81 -5
- package/templates/skills/apex/steps/step-01-analyze.md +27 -3
- package/templates/skills/apex/steps/step-02-plan.md +5 -1
- package/templates/skills/apex/steps/step-03-execute.md +47 -5
- package/templates/skills/apex/steps/step-04-validate.md +300 -0
- package/templates/skills/apex/steps/step-05-examine.md +7 -0
- package/templates/skills/apex/steps/step-07-tests.md +19 -0
- package/templates/skills/business-analyse/_shared.md +6 -6
- package/templates/skills/business-analyse/patterns/suggestion-catalog.md +1 -1
- package/templates/skills/business-analyse/questionnaire/07-ui.md +3 -3
- package/templates/skills/business-analyse/references/cadrage-pre-analysis.md +1 -1
- package/templates/skills/business-analyse/references/entity-architecture-decision.md +3 -3
- package/templates/skills/business-analyse/references/handoff-file-templates.md +13 -5
- package/templates/skills/business-analyse/references/spec-auto-inference.md +14 -14
- package/templates/skills/business-analyse/steps/step-01-cadrage.md +2 -2
- package/templates/skills/business-analyse/steps/step-02-decomposition.md +1 -1
- package/templates/skills/business-analyse/steps/step-03a1-setup.md +2 -2
- package/templates/skills/business-analyse/steps/step-03b-ui.md +2 -1
- package/templates/skills/business-analyse/steps/step-05a-handoff.md +15 -4
- package/templates/skills/business-analyse/templates/tpl-frd.md +2 -2
- package/templates/skills/business-analyse/templates-frd.md +2 -2
- package/templates/skills/efcore/steps/migration/step-02-create.md +14 -1
- package/templates/skills/ralph-loop/references/category-rules.md +71 -9
- package/templates/skills/ralph-loop/references/compact-loop.md +3 -3
- package/templates/skills/ralph-loop/references/core-seed-data.md +10 -0
- package/templates/skills/ralph-loop/steps/step-02-execute.md +190 -1
- package/templates/skills/validate-feature/steps/step-01-compile.md +4 -1
- package/templates/skills/validate-feature/steps/step-05-db-validation.md +86 -1
|
@@ -192,14 +192,16 @@ var sectionRoute = $"{moduleRoute}/{ToKebabCase(sectionCode)}";
|
|
|
192
192
|
|
|
193
193
|
## Layer 2 — Frontend (parallel with I18n)
|
|
194
194
|
|
|
195
|
+
> **Detailed patterns:** See `references/smartstack-frontend.md` for complete code templates.
|
|
196
|
+
|
|
195
197
|
**Pages:** `src/pages/{ContextPascal}/{AppPascal}/{Module}/`
|
|
196
198
|
**Components:** `src/components/{Module}/`
|
|
197
199
|
|
|
198
200
|
| Action | Tool |
|
|
199
201
|
|--------|------|
|
|
200
202
|
| API client | MCP `scaffold_api_client` |
|
|
201
|
-
| Routes | MCP `scaffold_routes` |
|
|
202
|
-
| Complex pages | /ui-components skill |
|
|
203
|
+
| Routes | MCP `scaffold_routes` (outputFormat: `clientRoutes`) |
|
|
204
|
+
| Complex pages | /ui-components skill + `smartstack-frontend.md` patterns |
|
|
203
205
|
| Validate routes | MCP `validate_frontend_routes` |
|
|
204
206
|
|
|
205
207
|
**Layout mapping:**
|
|
@@ -210,21 +212,95 @@ var sectionRoute = $"{moduleRoute}/{ToKebabCase(sectionCode)}";
|
|
|
210
212
|
| `business.*` | `BusinessLayout` | `/business` |
|
|
211
213
|
| `personal.*` | `UserLayout` | `/personal/myspace` |
|
|
212
214
|
|
|
215
|
+
### Lazy Loading (MANDATORY)
|
|
216
|
+
|
|
217
|
+
ALL page components MUST be lazy-loaded using `React.lazy()`:
|
|
218
|
+
|
|
219
|
+
```tsx
|
|
220
|
+
const EmployeesPage = lazy(() =>
|
|
221
|
+
import('@/pages/Business/HumanResources/Employees/EmployeesPage')
|
|
222
|
+
.then(m => ({ default: m.EmployeesPage }))
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
// Route element — ALWAYS wrap with Suspense
|
|
226
|
+
element: <Suspense fallback={<PageLoader />}><EmployeesPage /></Suspense>
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Page Structure Pattern
|
|
230
|
+
|
|
231
|
+
All pages MUST follow: hooks → useEffect(load) → loading state → error state → content.
|
|
232
|
+
|
|
233
|
+
```tsx
|
|
234
|
+
// 1. Hooks at top
|
|
235
|
+
const { t } = useTranslation(['{module}']);
|
|
236
|
+
// 2. State
|
|
237
|
+
const [loading, setLoading] = useState(true);
|
|
238
|
+
// 3. useCallback + useEffect for data loading
|
|
239
|
+
// 4. if (loading) return <Loader2 spinner />
|
|
240
|
+
// 5. if (error) return <error UI with retry>
|
|
241
|
+
// 6. return <content with CSS variables>
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Components & CSS
|
|
245
|
+
|
|
213
246
|
**Components:** SmartTable, SmartFilter, EntityCard, SmartForm, StatCard (NEVER raw HTML)
|
|
214
|
-
**CSS:** Variables only → `bg-[var(--bg-card)]`, `text-[var(--text-primary)]`
|
|
247
|
+
**CSS:** Variables only → `bg-[var(--bg-card)]`, `text-[var(--text-primary)]`, `border-[var(--border-color)]`
|
|
248
|
+
**Loader:** `Loader2` from `lucide-react` for spinners, `PageLoader` for Suspense fallback
|
|
249
|
+
|
|
250
|
+
### FK Fields in Forms (CRITICAL)
|
|
251
|
+
|
|
252
|
+
Any entity property that is a FK Guid (e.g., `EmployeeId`, `DepartmentId`) MUST use the `EntityLookup` component — NEVER a plain text input. Users cannot type GUIDs manually.
|
|
253
|
+
|
|
254
|
+
```tsx
|
|
255
|
+
// CORRECT — EntityLookup for FK field
|
|
256
|
+
<EntityLookup
|
|
257
|
+
apiEndpoint="/api/business/human-resources/employees"
|
|
258
|
+
value={formData.employeeId}
|
|
259
|
+
onChange={(id) => handleChange('employeeId', id)}
|
|
260
|
+
label={t('module:form.employee', 'Employee')}
|
|
261
|
+
mapOption={(item) => ({ id: item.id, label: item.name, sublabel: item.code })}
|
|
262
|
+
required
|
|
263
|
+
/>
|
|
264
|
+
|
|
265
|
+
// WRONG — plain text input for FK GUID
|
|
266
|
+
<input type="text" value={formData.employeeId} placeholder="Enter employee ID" />
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**Backend requirement:** Each target entity's GetAll endpoint MUST accept `?search=` parameter. See `smartstack-api.md` Service Pattern.
|
|
270
|
+
|
|
271
|
+
See `references/smartstack-frontend.md` section 6 for the full `EntityLookup` component definition and usage patterns.
|
|
215
272
|
|
|
216
273
|
**FORBIDDEN:**
|
|
217
274
|
- `src/pages/{Module}/` (flat, missing Context/App)
|
|
218
275
|
- `import axios` → use `@/services/api/apiClient`
|
|
219
276
|
- `<table>` → use SmartTable
|
|
220
|
-
-
|
|
277
|
+
- `<input type="text">` for FK Guid fields → use `EntityLookup`
|
|
278
|
+
- Placeholder "Enter ID" or "Enter GUID" → use `EntityLookup`
|
|
279
|
+
- Hardcoded Tailwind colors (`bg-white`, `text-gray-900`) → use CSS variables
|
|
280
|
+
- Static page imports in route files → use `React.lazy()`
|
|
281
|
+
- `<Suspense>` without `fallback` prop
|
|
221
282
|
- Only fr/en → MUST have 4 languages
|
|
283
|
+
- `t('key')` without namespace prefix → use `t('ns:key', 'fallback')`
|
|
284
|
+
- Hardcoded user-facing strings in JSX → use `t()`
|
|
222
285
|
|
|
223
286
|
---
|
|
224
287
|
|
|
225
288
|
## Layer 2 — I18n
|
|
226
289
|
|
|
227
|
-
|
|
290
|
+
> **Template:** See `references/smartstack-frontend.md` Section 2 for complete JSON template.
|
|
291
|
+
|
|
292
|
+
4 JSON files per module namespace: fr, en, it, de — identical key structures.
|
|
293
|
+
|
|
294
|
+
**Location:** `src/i18n/locales/{lang}/{module}.json`
|
|
295
|
+
|
|
296
|
+
**Required keys:** `title`, `description`, `actions`, `labels`, `columns`, `form`, `errors`, `validation`, `messages`, `empty`
|
|
297
|
+
|
|
298
|
+
**Usage pattern:**
|
|
299
|
+
```tsx
|
|
300
|
+
const { t } = useTranslation(['{module}']);
|
|
301
|
+
t('{module}:title', 'Module Title') // ALWAYS with fallback
|
|
302
|
+
t('{module}:actions.create', 'Create entity') // ALWAYS with namespace prefix
|
|
303
|
+
```
|
|
228
304
|
|
|
229
305
|
---
|
|
230
306
|
|
|
@@ -136,7 +136,31 @@ Identify:
|
|
|
136
136
|
|
|
137
137
|
---
|
|
138
138
|
|
|
139
|
-
## 4.
|
|
139
|
+
## 4. FK Field Detection
|
|
140
|
+
|
|
141
|
+
For each entity found (or to be created), identify FK relationships:
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
For each entity in analysis results:
|
|
145
|
+
- List properties ending in "Id" with a matching navigation property (e.g., EmployeeId + Employee)
|
|
146
|
+
- Record: { fkProperty, targetEntity, isRequired }
|
|
147
|
+
|
|
148
|
+
Output:
|
|
149
|
+
fkFields: [
|
|
150
|
+
{ entity: "TimeEntry", fkProperty: "EmployeeId", targetEntity: "Employee", isRequired: true },
|
|
151
|
+
{ entity: "TimeEntry", fkProperty: "ProjectId", targetEntity: "Project", isRequired: true }
|
|
152
|
+
]
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Why:** FK fields drive two critical downstream requirements:
|
|
156
|
+
1. **Frontend:** Each FK field MUST use `EntityLookup` component (NEVER plain text input for GUIDs)
|
|
157
|
+
2. **Backend:** Each target entity's GetAll endpoint MUST support `?search=` query parameter
|
|
158
|
+
|
|
159
|
+
These are propagated to step-02 (plan) and step-03 (execute).
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## 5. Gap Analysis
|
|
140
164
|
|
|
141
165
|
Compare what EXISTS vs what the TASK REQUIRES:
|
|
142
166
|
|
|
@@ -149,7 +173,7 @@ For each element in the task description:
|
|
|
149
173
|
|
|
150
174
|
---
|
|
151
175
|
|
|
152
|
-
##
|
|
176
|
+
## 6. Analysis Summary
|
|
153
177
|
|
|
154
178
|
```
|
|
155
179
|
**APEX SmartStack - Analysis Complete**
|
|
@@ -176,7 +200,7 @@ For each element in the task description:
|
|
|
176
200
|
|
|
177
201
|
---
|
|
178
202
|
|
|
179
|
-
##
|
|
203
|
+
## 7. Save Output (if save_mode)
|
|
180
204
|
|
|
181
205
|
Write to `{output_dir}/01-analyze.md` with analysis results.
|
|
182
206
|
|
|
@@ -63,9 +63,13 @@ For EACH file in the plan, specify HOW it will be created/modified:
|
|
|
63
63
|
| # | File | Action | Tool |
|
|
64
64
|
|---|------|--------|------|
|
|
65
65
|
| 10 | src/pages/{Ctx}/{App}/{Mod}/ListPage.tsx | create | /ui-components skill |
|
|
66
|
+
| 10b | src/pages/{Ctx}/{App}/{Mod}/CreatePage.tsx | create | /ui-components skill (EntityLookup for FK fields) |
|
|
67
|
+
| 10c | src/pages/{Ctx}/{App}/{Mod}/EditPage.tsx | create | /ui-components skill (EntityLookup for FK fields) |
|
|
66
68
|
| 11 | src/services/api/{module}Api.ts | create | MCP scaffold_api_client |
|
|
67
69
|
| 12 | src/routes/{module}.tsx | create | MCP scaffold_routes |
|
|
68
|
-
| 13 | src/locales/
|
|
70
|
+
| 13 | src/i18n/locales/{lang}/{module}.json | create | ref smartstack-frontend.md (4 languages: fr, en, it, de) |
|
|
71
|
+
|
|
72
|
+
**FK Field Guidance:** If step-01 identified `fkFields[]`, every Create/Edit page MUST use `EntityLookup` for those fields (see `smartstack-frontend.md` section 6). The corresponding backend GetAll endpoints (Layer 1) MUST support `?search=` parameter.
|
|
69
73
|
|
|
70
74
|
### Layer 3 — Tests (sequential, if -t)
|
|
71
75
|
|
|
@@ -14,6 +14,7 @@ All code goes through skills (/controller, /application, /ui-components, /efcore
|
|
|
14
14
|
## LOAD CONDITIONALLY
|
|
15
15
|
|
|
16
16
|
- **ALWAYS** read `references/smartstack-api.md` — BaseEntity API, entity/config/controller patterns
|
|
17
|
+
- **ALWAYS** read `references/smartstack-frontend.md` — lazy loading, i18n, page structure, CSS variables
|
|
17
18
|
- Read `references/smartstack-layers.md` for execution rules per layer
|
|
18
19
|
- If NOT `{economy_mode}` AND Layer 1 has parallel work: read `references/agent-teams-protocol.md`
|
|
19
20
|
|
|
@@ -44,8 +45,10 @@ For each entity:
|
|
|
44
45
|
```
|
|
45
46
|
1. MCP suggest_migration → get standardized name
|
|
46
47
|
2. dotnet ef migrations add {Name} --project src/{Infra}.csproj --startup-project src/{Api}.csproj -o Persistence/Migrations
|
|
47
|
-
3.
|
|
48
|
-
|
|
48
|
+
3. Cleanup corrupted EF Core artifacts:
|
|
49
|
+
for d in src/*/bin?Debug; do [ -d "$d" ] && rm -rf "$d"; done
|
|
50
|
+
4. dotnet ef database update (if local DB)
|
|
51
|
+
5. dotnet build → MUST PASS
|
|
49
52
|
```
|
|
50
53
|
|
|
51
54
|
**BLOCKING:** If build fails after migration, fix EF configs before proceeding.
|
|
@@ -66,6 +69,18 @@ dotnet build
|
|
|
66
69
|
|
|
67
70
|
Execute each item from the plan sequentially using skills and MCP.
|
|
68
71
|
|
|
72
|
+
**For frontend tasks (economy_mode):** Follow the same rules as exec-frontend above:
|
|
73
|
+
- `MCP scaffold_api_client` → API client + types + React Query hook
|
|
74
|
+
- `MCP scaffold_routes` with `outputFormat: 'clientRoutes'` for lazy imports
|
|
75
|
+
- Pages: use `/ui-components` skill — MANDATORY for entity lists, grids, tables, dashboards
|
|
76
|
+
- **Form pages: Create `EntityCreatePage.tsx` (route: `/create`) and `EntityEditPage.tsx` (route: `/:id/edit`)**
|
|
77
|
+
- **FK FIELDS (CRITICAL):** Any Guid FK property (e.g., EmployeeId, DepartmentId) MUST use `EntityLookup` component — NEVER a plain text input. See `smartstack-frontend.md` section 6.
|
|
78
|
+
- **ZERO modals/popups/drawers for forms — ALL forms are full pages with their own URL**
|
|
79
|
+
- **Form tests: Generate `EntityCreatePage.test.tsx` and `EntityEditPage.test.tsx` (co-located)**
|
|
80
|
+
- Read `references/smartstack-frontend.md` for mandatory patterns (sections 3b + 8)
|
|
81
|
+
- Generate i18n JSON files for all 4 languages (fr, en, it, de) — `src/i18n/locales/{lang}/{module}.json`
|
|
82
|
+
- All pages must follow loading → error → content pattern with CSS variables
|
|
83
|
+
|
|
69
84
|
### If NOT economy_mode: Agent Teams (parallel)
|
|
70
85
|
|
|
71
86
|
> **Protocol:** See `references/agent-teams-protocol.md`
|
|
@@ -82,6 +97,7 @@ Spawn 2 teammates (Opus, full tools):
|
|
|
82
97
|
For each task:
|
|
83
98
|
- Application services/DTOs: MCP scaffold_extension
|
|
84
99
|
- Controllers: use /controller skill for complex, MCP scaffold_extension for simple
|
|
100
|
+
- IMPORTANT: ALL GetAll endpoints MUST support ?search= query parameter (enables EntityLookup on frontend)
|
|
85
101
|
- Seed data: MCP generate_permissions, then follow smartstack-layers.md templates
|
|
86
102
|
|
|
87
103
|
After ALL tasks done:
|
|
@@ -91,11 +107,37 @@ Spawn 2 teammates (Opus, full tools):
|
|
|
91
107
|
"Execute these Layer 1 frontend tasks for module {module_code}:
|
|
92
108
|
{list of frontend + i18n tasks from plan}
|
|
93
109
|
|
|
110
|
+
**MANDATORY: Read references/smartstack-frontend.md FIRST** — contains all required patterns.
|
|
111
|
+
|
|
94
112
|
For each task:
|
|
95
113
|
- API client: MCP scaffold_api_client
|
|
96
|
-
- Routes: MCP scaffold_routes
|
|
97
|
-
- Pages: use /ui-components skill
|
|
98
|
-
|
|
114
|
+
- Routes: MCP scaffold_routes (outputFormat: 'clientRoutes') → generates lazy imports + Suspense
|
|
115
|
+
- Pages: use /ui-components skill — MUST follow smartstack-frontend.md patterns:
|
|
116
|
+
→ React.lazy() for all page imports (named export wrapping)
|
|
117
|
+
→ <Suspense fallback={<PageLoader />}> around all lazy components
|
|
118
|
+
→ Page structure: hooks → useEffect(load) → loading → error → content
|
|
119
|
+
→ CSS variables only: bg-[var(--bg-card)], text-[var(--text-primary)]
|
|
120
|
+
→ SmartTable/SmartFilter/EntityCard (NEVER raw HTML)
|
|
121
|
+
- **FORM PAGES (CRITICAL):** Create/Edit forms are FULL PAGES with own routes:
|
|
122
|
+
→ EntityCreatePage.tsx with route /{module}/create
|
|
123
|
+
→ EntityEditPage.tsx with route /{module}/:id/edit
|
|
124
|
+
→ ZERO modals/popups/drawers/dialogs for forms
|
|
125
|
+
→ Back button with navigate(-1) on every form page
|
|
126
|
+
→ See smartstack-frontend.md section 3b for templates
|
|
127
|
+
- **FK FIELDS (CRITICAL):** Foreign key Guid fields (e.g., EmployeeId) MUST use EntityLookup:
|
|
128
|
+
→ NEVER render FK fields as plain text inputs — users cannot type GUIDs
|
|
129
|
+
→ Use EntityLookup from @/components/ui/EntityLookup for searchable selection
|
|
130
|
+
→ Each FK field needs: apiEndpoint, mapOption (display name), search support on backend
|
|
131
|
+
→ See smartstack-frontend.md section 6 for the full EntityLookup pattern
|
|
132
|
+
- **FORM TESTS (MANDATORY):**
|
|
133
|
+
→ EntityCreatePage.test.tsx (co-located next to page)
|
|
134
|
+
→ EntityEditPage.test.tsx (co-located next to page)
|
|
135
|
+
→ Cover: rendering, validation, submit, pre-fill, navigation, errors
|
|
136
|
+
→ See smartstack-frontend.md section 8 for test templates
|
|
137
|
+
- I18n: Generate 4 JSON files per module namespace (fr, en, it, de)
|
|
138
|
+
→ Follow JSON template from smartstack-frontend.md
|
|
139
|
+
→ Keys: actions, labels, errors, validation, columns, form, messages, empty
|
|
140
|
+
→ ALL t() calls MUST use namespace prefix + fallback: t('ns:key', 'Default text')
|
|
99
141
|
- MUST use src/pages/{Context}/{App}/{Module}/ hierarchy (NOT flat)
|
|
100
142
|
|
|
101
143
|
After ALL tasks done:
|
|
@@ -54,6 +54,9 @@ Verify:
|
|
|
54
54
|
## 4. Build Verification
|
|
55
55
|
|
|
56
56
|
```bash
|
|
57
|
+
# Cleanup corrupted EF Core design-time artifacts (Roslyn BuildHost bug on Windows)
|
|
58
|
+
for d in src/*/bin?Debug; do [ -d "$d" ] && echo "Removing corrupted artifact: $d" && rm -rf "$d"; done
|
|
59
|
+
|
|
57
60
|
# Backend
|
|
58
61
|
dotnet clean && dotnet restore && dotnet build
|
|
59
62
|
|
|
@@ -222,6 +225,290 @@ if [ -n "$SERVICE_FILES" ]; then
|
|
|
222
225
|
fi
|
|
223
226
|
```
|
|
224
227
|
|
|
228
|
+
### POST-CHECK 6: Translation files must exist for all 4 languages (if frontend)
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
# Find all i18n namespaces used in tsx files
|
|
232
|
+
TSX_FILES=$(find src/pages/ -name "*.tsx" 2>/dev/null)
|
|
233
|
+
if [ -n "$TSX_FILES" ]; then
|
|
234
|
+
NAMESPACES=$(grep -ohP "useTranslation\(\[?'([^']+)" $TSX_FILES | sed "s/.*'//" | sort -u)
|
|
235
|
+
for NS in $NAMESPACES; do
|
|
236
|
+
for LANG in fr en it de; do
|
|
237
|
+
if [ ! -f "src/i18n/locales/$LANG/$NS.json" ]; then
|
|
238
|
+
echo "BLOCKING: Missing translation file: src/i18n/locales/$LANG/$NS.json"
|
|
239
|
+
exit 1
|
|
240
|
+
fi
|
|
241
|
+
done
|
|
242
|
+
done
|
|
243
|
+
fi
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### POST-CHECK 7: Pages must use lazy loading (no static page imports in routes)
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
ROUTE_FILES=$(find src/routes/ -name "*.tsx" -o -name "*.ts" 2>/dev/null)
|
|
250
|
+
if [ -n "$ROUTE_FILES" ]; then
|
|
251
|
+
STATIC_PAGE_IMPORTS=$(grep -Pn "^import .+ from '@/pages/" $ROUTE_FILES 2>/dev/null)
|
|
252
|
+
if [ -n "$STATIC_PAGE_IMPORTS" ]; then
|
|
253
|
+
echo "BLOCKING: Route files must use React.lazy() for page imports, not static imports"
|
|
254
|
+
echo "$STATIC_PAGE_IMPORTS"
|
|
255
|
+
echo "Fix: const Page = lazy(() => import('@/pages/...').then(m => ({ default: m.Page })));"
|
|
256
|
+
exit 1
|
|
257
|
+
fi
|
|
258
|
+
fi
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### POST-CHECK 8: Forms must be full pages with routes — ZERO modals/popups/drawers
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
# Check for modal/dialog/drawer imports in page files (create/edit forms)
|
|
265
|
+
PAGE_FILES=$(find src/pages/ -name "*.tsx" 2>/dev/null)
|
|
266
|
+
if [ -n "$PAGE_FILES" ]; then
|
|
267
|
+
MODAL_IMPORTS=$(grep -Pn "import.*(?:Modal|Dialog|Drawer|Popup|Sheet)" $PAGE_FILES 2>/dev/null)
|
|
268
|
+
if [ -n "$MODAL_IMPORTS" ]; then
|
|
269
|
+
echo "BLOCKING: Form pages must NOT use Modal/Dialog/Drawer/Popup components"
|
|
270
|
+
echo "Create/Edit forms MUST be full pages with their own URL routes"
|
|
271
|
+
echo "Found modal imports in page files:"
|
|
272
|
+
echo "$MODAL_IMPORTS"
|
|
273
|
+
echo "Fix: Create EntityCreatePage.tsx with route /create and EntityEditPage.tsx with route /:id/edit"
|
|
274
|
+
exit 1
|
|
275
|
+
fi
|
|
276
|
+
fi
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### POST-CHECK 9: Create/Edit pages must exist as separate route pages
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
# For each module with a list page, verify create and edit pages exist
|
|
283
|
+
LIST_PAGES=$(find src/pages/ -name "*ListPage.tsx" -o -name "*sPage.tsx" 2>/dev/null | grep -v test)
|
|
284
|
+
if [ -n "$LIST_PAGES" ]; then
|
|
285
|
+
for LIST_PAGE in $LIST_PAGES; do
|
|
286
|
+
PAGE_DIR=$(dirname "$LIST_PAGE")
|
|
287
|
+
MODULE_NAME=$(basename "$PAGE_DIR")
|
|
288
|
+
# Check for create page
|
|
289
|
+
CREATE_PAGE=$(find "$PAGE_DIR" -name "*CreatePage.tsx" 2>/dev/null)
|
|
290
|
+
if [ -z "$CREATE_PAGE" ]; then
|
|
291
|
+
echo "WARNING: Module $MODULE_NAME has a list page but no CreatePage — expected EntityCreatePage.tsx"
|
|
292
|
+
fi
|
|
293
|
+
# Check for edit page
|
|
294
|
+
EDIT_PAGE=$(find "$PAGE_DIR" -name "*EditPage.tsx" 2>/dev/null)
|
|
295
|
+
if [ -z "$EDIT_PAGE" ]; then
|
|
296
|
+
echo "WARNING: Module $MODULE_NAME has a list page but no EditPage — expected EntityEditPage.tsx"
|
|
297
|
+
fi
|
|
298
|
+
done
|
|
299
|
+
fi
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### POST-CHECK 10: Form pages must have companion test files
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
# Minimum requirement: if frontend pages exist, at least 1 test file must be present
|
|
306
|
+
PAGE_FILES=$(find src/pages/ -name "*.tsx" ! -name "*.test.tsx" 2>/dev/null)
|
|
307
|
+
if [ -n "$PAGE_FILES" ]; then
|
|
308
|
+
ALL_TESTS=$(find src/pages/ -name "*.test.tsx" 2>/dev/null)
|
|
309
|
+
if [ -z "$ALL_TESTS" ]; then
|
|
310
|
+
echo "BLOCKING: No frontend test files found in src/pages/"
|
|
311
|
+
echo "Every form page MUST have a companion .test.tsx file"
|
|
312
|
+
exit 1
|
|
313
|
+
fi
|
|
314
|
+
fi
|
|
315
|
+
|
|
316
|
+
# Every CreatePage and EditPage must have a .test.tsx file
|
|
317
|
+
FORM_PAGES=$(find src/pages/ -name "*CreatePage.tsx" -o -name "*EditPage.tsx" 2>/dev/null | grep -v test)
|
|
318
|
+
if [ -n "$FORM_PAGES" ]; then
|
|
319
|
+
for FORM_PAGE in $FORM_PAGES; do
|
|
320
|
+
TEST_FILE="${FORM_PAGE%.tsx}.test.tsx"
|
|
321
|
+
if [ ! -f "$TEST_FILE" ]; then
|
|
322
|
+
echo "BLOCKING: Form page missing test file: $FORM_PAGE"
|
|
323
|
+
echo "Expected: $TEST_FILE"
|
|
324
|
+
echo "All form pages MUST have companion test files (rendering, validation, submit, navigation)"
|
|
325
|
+
exit 1
|
|
326
|
+
fi
|
|
327
|
+
done
|
|
328
|
+
fi
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### POST-CHECK 11: FK fields must NOT be plain text inputs (EntityLookup required)
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
# Check for FK fields rendered as plain text inputs in form pages
|
|
335
|
+
FORM_PAGES=$(find src/pages/ -name "*CreatePage.tsx" -o -name "*EditPage.tsx" 2>/dev/null | grep -v test | grep -v node_modules)
|
|
336
|
+
if [ -n "$FORM_PAGES" ]; then
|
|
337
|
+
# Detect FK input fields: <input> with name ending in "Id" (catches inputs with or without explicit type)
|
|
338
|
+
FK_INPUTS=$(grep -Pn '<input[^>]*name=["\x27][a-zA-Z]*Id["\x27]' $FORM_PAGES 2>/dev/null)
|
|
339
|
+
if [ -n "$FK_INPUTS" ]; then
|
|
340
|
+
# Filter out hidden inputs (legitimate for FK submission)
|
|
341
|
+
FK_TEXT_INPUTS=$(echo "$FK_INPUTS" | grep -Pv 'type=["\x27]hidden["\x27]')
|
|
342
|
+
if [ -n "$FK_TEXT_INPUTS" ]; then
|
|
343
|
+
echo "BLOCKING: FK fields rendered as plain text inputs — MUST use EntityLookup component"
|
|
344
|
+
echo "Users cannot type GUIDs manually. Use <EntityLookup /> from @/components/ui/EntityLookup"
|
|
345
|
+
echo "See smartstack-frontend.md section 6 for the EntityLookup pattern"
|
|
346
|
+
echo "$FK_TEXT_INPUTS"
|
|
347
|
+
exit 1
|
|
348
|
+
fi
|
|
349
|
+
fi
|
|
350
|
+
|
|
351
|
+
# Check for input placeholders mentioning "ID" or "id" or "Enter...Id"
|
|
352
|
+
FK_PLACEHOLDER=$(grep -Pn 'placeholder=["\x27].*[Ee]nter.*[Ii][Dd]|placeholder=["\x27].*[Gg][Uu][Ii][Dd]' $FORM_PAGES 2>/dev/null)
|
|
353
|
+
if [ -n "$FK_PLACEHOLDER" ]; then
|
|
354
|
+
echo "BLOCKING: Form has placeholder asking user to enter an ID/GUID — use EntityLookup instead"
|
|
355
|
+
echo "$FK_PLACEHOLDER"
|
|
356
|
+
exit 1
|
|
357
|
+
fi
|
|
358
|
+
fi
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### POST-CHECK 12: Backend APIs must support search parameter for EntityLookup
|
|
362
|
+
|
|
363
|
+
```bash
|
|
364
|
+
# Check that controller GetAll methods accept search parameter
|
|
365
|
+
CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
|
|
366
|
+
if [ -n "$CTRL_FILES" ]; then
|
|
367
|
+
for f in $CTRL_FILES; do
|
|
368
|
+
# Check if controller has GetAll but no search parameter
|
|
369
|
+
if grep -q "\[HttpGet\]" "$f" && grep -q "GetAll" "$f"; then
|
|
370
|
+
if ! grep -q "search" "$f"; then
|
|
371
|
+
echo "WARNING: Controller missing search parameter on GetAll: $f"
|
|
372
|
+
echo "GetAll endpoints MUST accept ?search= to enable EntityLookup on frontend"
|
|
373
|
+
echo "Fix: Add [FromQuery] string? search parameter to GetAll action"
|
|
374
|
+
fi
|
|
375
|
+
fi
|
|
376
|
+
done
|
|
377
|
+
fi
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### POST-CHECK 13: No hardcoded Tailwind colors in generated pages
|
|
381
|
+
|
|
382
|
+
```bash
|
|
383
|
+
# Scan all page and component files directly (works for uncommitted/untracked files, Windows/WSL compatible)
|
|
384
|
+
ALL_PAGES=$(find src/pages/ src/components/ -name "*.tsx" 2>/dev/null | grep -v node_modules | grep -v "\.test\.")
|
|
385
|
+
if [ -n "$ALL_PAGES" ]; then
|
|
386
|
+
HARDCODED=$(grep -Pn '(bg|text|border)-(?!\[)(red|blue|green|gray|white|black|slate|zinc|neutral|stone)-' $ALL_PAGES 2>/dev/null)
|
|
387
|
+
if [ -n "$HARDCODED" ]; then
|
|
388
|
+
echo "WARNING: Pages should use CSS variables instead of hardcoded Tailwind colors"
|
|
389
|
+
echo "Fix: bg-[var(--bg-card)] instead of bg-white, text-[var(--text-primary)] instead of text-gray-900"
|
|
390
|
+
echo "$HARDCODED"
|
|
391
|
+
fi
|
|
392
|
+
fi
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### POST-CHECK 14: Routes seed data must match frontend contextRoutes
|
|
396
|
+
|
|
397
|
+
```bash
|
|
398
|
+
SEED_ROUTES=$(grep -Poh 'Route\s*=\s*"([^"]+)"' $(find src/ -path "*/Seeding/Data/*" -name "*NavigationSeedData.cs" 2>/dev/null) 2>/dev/null | grep -oP '"[^"]+"' | tr -d '"' | sort -u)
|
|
399
|
+
APP_TSX=$(find src/ -name "App.tsx" -not -path "*/node_modules/*" 2>/dev/null | head -1)
|
|
400
|
+
if [ -n "$APP_TSX" ] && [ -n "$SEED_ROUTES" ]; then
|
|
401
|
+
FRONTEND_PATHS=$(grep -oP "path:\s*'([^']+)'" "$APP_TSX" | grep -oP "'[^']+'" | tr -d "'" | sort -u)
|
|
402
|
+
if [ -n "$FRONTEND_PATHS" ]; then
|
|
403
|
+
MISMATCH_FOUND=false
|
|
404
|
+
for SEED_ROUTE in $SEED_ROUTES; do
|
|
405
|
+
DEPTH=$(echo "$SEED_ROUTE" | tr '/' '\n' | grep -c '.')
|
|
406
|
+
if [ "$DEPTH" -lt 3 ]; then continue; fi
|
|
407
|
+
SEED_SUFFIX=$(echo "$SEED_ROUTE" | sed 's|^/[^/]*/||')
|
|
408
|
+
SEED_NORM=$(echo "$SEED_SUFFIX" | tr '[:upper:]' '[:lower:]' | tr -d '-')
|
|
409
|
+
MATCH_FOUND=false
|
|
410
|
+
for FE_PATH in $FRONTEND_PATHS; do
|
|
411
|
+
FE_BASE=$(echo "$FE_PATH" | sed 's|/list$||;s|/new$||;s|/:id.*||;s|/create$||')
|
|
412
|
+
FE_NORM=$(echo "$FE_BASE" | tr '[:upper:]' '[:lower:]' | tr -d '-')
|
|
413
|
+
if [ "$SEED_NORM" = "$FE_NORM" ]; then
|
|
414
|
+
MATCH_FOUND=true
|
|
415
|
+
break
|
|
416
|
+
fi
|
|
417
|
+
done
|
|
418
|
+
if [ "$MATCH_FOUND" = false ]; then
|
|
419
|
+
echo "BLOCKING: Seed data route has no matching frontend route: $SEED_ROUTE"
|
|
420
|
+
MISMATCH_FOUND=true
|
|
421
|
+
fi
|
|
422
|
+
done
|
|
423
|
+
if [ "$MISMATCH_FOUND" = true ]; then
|
|
424
|
+
echo "Fix: Ensure every NavigationSeedData route has a corresponding contextRoutes entry in App.tsx"
|
|
425
|
+
exit 1
|
|
426
|
+
fi
|
|
427
|
+
fi
|
|
428
|
+
fi
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### POST-CHECK 15: HasQueryFilter must not use Guid.Empty (OWASP A01)
|
|
432
|
+
|
|
433
|
+
```bash
|
|
434
|
+
CONFIG_FILES=$(find src/ -path "*/Configurations/*" -name "*Configuration.cs" 2>/dev/null)
|
|
435
|
+
if [ -n "$CONFIG_FILES" ]; then
|
|
436
|
+
BAD_FILTERS=$(grep -Pn 'HasQueryFilter.*Guid\.Empty' $CONFIG_FILES 2>/dev/null)
|
|
437
|
+
if [ -n "$BAD_FILTERS" ]; then
|
|
438
|
+
echo "BLOCKING (OWASP A01): HasQueryFilter uses Guid.Empty instead of runtime tenant isolation"
|
|
439
|
+
echo "$BAD_FILTERS"
|
|
440
|
+
echo ""
|
|
441
|
+
echo "Anti-pattern: .HasQueryFilter(e => e.TenantId != Guid.Empty)"
|
|
442
|
+
echo "Fix: Remove HasQueryFilter. Tenant isolation is handled by SmartStack base DbContext"
|
|
443
|
+
exit 1
|
|
444
|
+
fi
|
|
445
|
+
fi
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### POST-CHECK 16: GetAll methods must return PaginatedResult<T>
|
|
449
|
+
|
|
450
|
+
```bash
|
|
451
|
+
SERVICE_FILES=$(find src/ -path "*/Services/*" -name "*Service.cs" ! -name "I*Service.cs" 2>/dev/null)
|
|
452
|
+
if [ -n "$SERVICE_FILES" ]; then
|
|
453
|
+
BAD_RETURNS=$(grep -Pn '(Task<\s*(?:List|IEnumerable|IList|ICollection|IReadOnlyList|IReadOnlyCollection)<).*GetAll' $SERVICE_FILES 2>/dev/null)
|
|
454
|
+
if [ -n "$BAD_RETURNS" ]; then
|
|
455
|
+
echo "BLOCKING: GetAll methods must return PaginatedResult<T>, not List/IEnumerable"
|
|
456
|
+
echo "$BAD_RETURNS"
|
|
457
|
+
echo "Fix: Change return type to Task<PaginatedResult<{Entity}ResponseDto>>"
|
|
458
|
+
exit 1
|
|
459
|
+
fi
|
|
460
|
+
fi
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
### POST-CHECK 17: i18n files must contain required structural keys
|
|
464
|
+
|
|
465
|
+
```bash
|
|
466
|
+
I18N_DIR="src/i18n/locales/fr"
|
|
467
|
+
if [ -d "$I18N_DIR" ]; then
|
|
468
|
+
REQUIRED_KEYS="actions columns empty errors form labels messages validation"
|
|
469
|
+
for JSON_FILE in "$I18N_DIR"/*.json; do
|
|
470
|
+
[ ! -f "$JSON_FILE" ] && continue
|
|
471
|
+
BASENAME=$(basename "$JSON_FILE")
|
|
472
|
+
case "$BASENAME" in common.json|navigation.json) continue;; esac
|
|
473
|
+
for KEY in $REQUIRED_KEYS; do
|
|
474
|
+
if ! jq -e "has(\"$KEY\")" "$JSON_FILE" > /dev/null 2>&1; then
|
|
475
|
+
echo "BLOCKING: i18n file missing required key '$KEY': $JSON_FILE"
|
|
476
|
+
echo "Module i18n files MUST contain: $REQUIRED_KEYS"
|
|
477
|
+
exit 1
|
|
478
|
+
fi
|
|
479
|
+
done
|
|
480
|
+
done
|
|
481
|
+
fi
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### POST-CHECK 18: Entities must implement IAuditableEntity + Validators must have Create/Update pairs
|
|
485
|
+
|
|
486
|
+
```bash
|
|
487
|
+
ENTITY_FILES=$(find src/ -path "*/Domain/Entities/*" -name "*.cs" 2>/dev/null)
|
|
488
|
+
if [ -n "$ENTITY_FILES" ]; then
|
|
489
|
+
for f in $ENTITY_FILES; do
|
|
490
|
+
if grep -q "ITenantEntity" "$f" && ! grep -q "IAuditableEntity" "$f"; then
|
|
491
|
+
echo "BLOCKING: Entity implements ITenantEntity but NOT IAuditableEntity: $f"
|
|
492
|
+
echo "Pattern: public class Entity : BaseEntity, ITenantEntity, IAuditableEntity"
|
|
493
|
+
exit 1
|
|
494
|
+
fi
|
|
495
|
+
done
|
|
496
|
+
fi
|
|
497
|
+
CREATE_VALIDATORS=$(find src/ -path "*/Validators/*" -name "Create*Validator.cs" 2>/dev/null)
|
|
498
|
+
if [ -n "$CREATE_VALIDATORS" ]; then
|
|
499
|
+
for f in $CREATE_VALIDATORS; do
|
|
500
|
+
VALIDATOR_DIR=$(dirname "$f")
|
|
501
|
+
ENTITY_NAME=$(basename "$f" | sed 's/^Create\(.*\)Validator\.cs$/\1/')
|
|
502
|
+
if [ ! -f "$VALIDATOR_DIR/Update${ENTITY_NAME}Validator.cs" ]; then
|
|
503
|
+
echo "BLOCKING: Create${ENTITY_NAME}Validator exists but Update${ENTITY_NAME}Validator is missing"
|
|
504
|
+
echo " Found: $f"
|
|
505
|
+
echo " Expected: $VALIDATOR_DIR/Update${ENTITY_NAME}Validator.cs"
|
|
506
|
+
exit 1
|
|
507
|
+
fi
|
|
508
|
+
done
|
|
509
|
+
fi
|
|
510
|
+
```
|
|
511
|
+
|
|
225
512
|
**If ANY POST-CHECK fails → fix in step-03, re-validate.**
|
|
226
513
|
|
|
227
514
|
---
|
|
@@ -255,6 +542,19 @@ AC2: {criterion} → PASS / FAIL (evidence: {file:line or test})
|
|
|
255
542
|
| dotnet build | PASS |
|
|
256
543
|
| npm typecheck | PASS / N/A |
|
|
257
544
|
| Seed data | PASS / N/A |
|
|
545
|
+
| I18n: 4 languages per namespace | PASS / N/A |
|
|
546
|
+
| Lazy loading: no static page imports | PASS / N/A |
|
|
547
|
+
| Forms: full pages, zero modals | PASS / N/A |
|
|
548
|
+
| Forms: create/edit pages exist | PASS / N/A |
|
|
549
|
+
| Forms: test files exist | PASS / N/A |
|
|
550
|
+
| FK fields: EntityLookup, no plain text | PASS / N/A |
|
|
551
|
+
| APIs: search parameter on GetAll | PASS / N/A |
|
|
552
|
+
| CSS variables: no hardcoded colors | PASS / N/A |
|
|
553
|
+
| Routes: seed data vs frontend match | PASS / N/A |
|
|
554
|
+
| HasQueryFilter: no Guid.Empty pattern | PASS / N/A |
|
|
555
|
+
| GetAll: PaginatedResult required | PASS / N/A |
|
|
556
|
+
| I18n: required key structure | PASS / N/A |
|
|
557
|
+
| Entities: IAuditableEntity + validators | PASS / N/A |
|
|
258
558
|
| Acceptance criteria | {X}/{Y} PASS |
|
|
259
559
|
```
|
|
260
560
|
|
|
@@ -60,6 +60,13 @@ For each changed file, check:
|
|
|
60
60
|
- [ ] SmartTable/SmartForm (not raw HTML tables/forms)
|
|
61
61
|
- [ ] Correct Layout wrapper per context
|
|
62
62
|
|
|
63
|
+
**FK Fields & Forms:**
|
|
64
|
+
- [ ] FK Guid fields use `EntityLookup` component (NEVER plain text `<input>`)
|
|
65
|
+
- [ ] No placeholder text asking user to "Enter ID" or "Enter GUID"
|
|
66
|
+
- [ ] Create/Edit forms are full pages with own routes (ZERO modals/dialogs)
|
|
67
|
+
- [ ] Backend GetAll endpoints support `?search=` param for EntityLookup
|
|
68
|
+
- [ ] Each EntityLookup has `apiEndpoint`, `mapOption`, `label` props
|
|
69
|
+
|
|
63
70
|
---
|
|
64
71
|
|
|
65
72
|
## 4. Produce Findings
|
|
@@ -100,6 +100,24 @@ Call: mcp__smartstack__scaffold_tests
|
|
|
100
100
|
Tests: RequirePermission enforcement, multi-tenant isolation
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
+
### Frontend Form Tests
|
|
104
|
+
```
|
|
105
|
+
For each module with create/edit pages:
|
|
106
|
+
→ Generate EntityCreatePage.test.tsx and EntityEditPage.test.tsx
|
|
107
|
+
→ Co-located next to the page component (NOT in __tests__/ folder)
|
|
108
|
+
|
|
109
|
+
Required test coverage:
|
|
110
|
+
- Rendering: form renders with all expected fields
|
|
111
|
+
- Validation: required fields show errors on empty submit
|
|
112
|
+
- Submission: successful submit calls API and navigates back
|
|
113
|
+
- Pre-fill (edit only): entity data loaded into fields
|
|
114
|
+
- Navigation: back/cancel button calls navigate(-1)
|
|
115
|
+
- Error handling: API error displays error message
|
|
116
|
+
|
|
117
|
+
Tools: Vitest + React Testing Library + @testing-library/user-event
|
|
118
|
+
Mock API: vi.mock() or MSW — NEVER real API calls
|
|
119
|
+
```
|
|
120
|
+
|
|
103
121
|
---
|
|
104
122
|
|
|
105
123
|
## 3. Suggest Additional Scenarios
|
|
@@ -136,6 +154,7 @@ If under 80%: identify uncovered paths, scaffold additional tests
|
|
|
136
154
|
| Application | {count} | {%} |
|
|
137
155
|
| API | {count} | {%} |
|
|
138
156
|
| Security | {count} | {%} |
|
|
157
|
+
| Frontend Forms | {count} | {%} |
|
|
139
158
|
|
|
140
159
|
**Total tests:** {count}
|
|
141
160
|
**Estimated coverage:** {%}
|
|
@@ -98,13 +98,13 @@ Level 1: Context (business)
|
|
|
98
98
|
### Standard Sections per Module
|
|
99
99
|
|
|
100
100
|
> **RULE: Sections are functional zones, NOT CRUD operations.**
|
|
101
|
-
> `create` and `edit` are
|
|
101
|
+
> `create` and `edit` are SEPARATE PAGES with their own URL routes (`/create` and `/:id/edit`).
|
|
102
102
|
> `detail` is a page with tabs accessible by clicking a row in `list` (not a standalone nav section).
|
|
103
103
|
|
|
104
104
|
| Section | Purpose | Typical Components |
|
|
105
105
|
|---------|---------|-------------------|
|
|
106
|
-
| `list` | **Main page**: entity grid + create
|
|
107
|
-
| `detail` | **Reached from list** (hidden nav, route `:id`): tabbed view of entity details + edit
|
|
106
|
+
| `list` | **Main page**: entity grid + create button (navigates to `/create` page) + click to detail | DataGrid, FilterBar, Pagination, CreateButton |
|
|
107
|
+
| `detail` | **Reached from list** (hidden nav, route `:id`): tabbed view of entity details + edit button (navigates to `/:id/edit` page) | BackButton, DetailHeader, StatusBadge, TabPanel, DetailCard, SmartTable (relations), Timeline (history) |
|
|
108
108
|
| `dashboard` | Module overview/KPIs (if analytics needed) | Charts, StatCards, RecentActivity |
|
|
109
109
|
| `approve` | Approval workflow queue (if validation workflow) | ApprovalPanel, StatusTimeline |
|
|
110
110
|
| `import` | Bulk data import (if volume > 100) | FileUpload, MappingTable, Preview |
|
|
@@ -115,9 +115,9 @@ Level 1: Context (business)
|
|
|
115
115
|
|
|
116
116
|
| NOT a sidebar section | What it really is |
|
|
117
117
|
|---------------|-------------------|
|
|
118
|
-
| `create` | Action button in `list` page →
|
|
119
|
-
| `edit` | Action in detail page →
|
|
120
|
-
| `delete` | Action with confirmation
|
|
118
|
+
| `create` | Action button in `list` page → navigates to `/create` page with form |
|
|
119
|
+
| `edit` | Action in list/detail page → navigates to `/:id/edit` page with pre-filled form |
|
|
120
|
+
| `delete` | Action with inline confirmation in `list` or detail |
|
|
121
121
|
| `search` | FilterBar component integrated in the `list` section |
|
|
122
122
|
|
|
123
123
|
> **Note on `detail`:** The detail page IS a `specification.sections[]` entry (with `navigation: "hidden"`) so it gets a wireframe, resources, and route. But it is NOT shown in the sidebar — it is reached by clicking a row in `list`. Every module with a `list` section MUST have a companion `detail` section.
|