@c0x12c/ai-toolkit 1.15.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/marketplace.json +16 -0
- package/.claude-plugin/plugin.json +12 -0
- package/README.md +439 -0
- package/VERSION +1 -0
- package/agents/design-critic.md +127 -0
- package/agents/idea-killer.md +72 -0
- package/agents/infrastructure-expert.md +49 -0
- package/agents/micronaut-backend-expert.md +45 -0
- package/agents/phase-reviewer.md +150 -0
- package/agents/research-planner.md +70 -0
- package/agents/solution-architect-cto.md +49 -0
- package/agents/sre-architect.md +49 -0
- package/agents/team-coordinator.md +111 -0
- package/bin/cli.js +780 -0
- package/claude-md/00-header.md +39 -0
- package/claude-md/01-core.md +105 -0
- package/claude-md/05-database.md +20 -0
- package/claude-md/11-backend-micronaut.md +19 -0
- package/claude-md/20-frontend-react.md +44 -0
- package/claude-md/25-ux-design.md +56 -0
- package/claude-md/30-infrastructure.md +24 -0
- package/claude-md/30-project-mgmt.md +119 -0
- package/claude-md/40-product.md +39 -0
- package/claude-md/50-ops.md +34 -0
- package/claude-md/60-research.md +27 -0
- package/claude-md/90-footer.md +21 -0
- package/commands/spartan/brainstorm.md +134 -0
- package/commands/spartan/brownfield.md +157 -0
- package/commands/spartan/build.md +435 -0
- package/commands/spartan/careful.md +94 -0
- package/commands/spartan/commit-message.md +112 -0
- package/commands/spartan/content.md +17 -0
- package/commands/spartan/context-save.md +161 -0
- package/commands/spartan/contribute.md +140 -0
- package/commands/spartan/daily.md +42 -0
- package/commands/spartan/debug.md +308 -0
- package/commands/spartan/deep-dive.md +55 -0
- package/commands/spartan/deploy.md +207 -0
- package/commands/spartan/e2e.md +264 -0
- package/commands/spartan/env-setup.md +166 -0
- package/commands/spartan/epic.md +199 -0
- package/commands/spartan/fe-review.md +181 -0
- package/commands/spartan/figma-to-code.md +260 -0
- package/commands/spartan/forensics.md +46 -0
- package/commands/spartan/freeze.md +84 -0
- package/commands/spartan/fundraise.md +53 -0
- package/commands/spartan/gate-review.md +229 -0
- package/commands/spartan/gsd-upgrade.md +376 -0
- package/commands/spartan/guard.md +42 -0
- package/commands/spartan/init-project.md +178 -0
- package/commands/spartan/init-rules.md +298 -0
- package/commands/spartan/interview.md +154 -0
- package/commands/spartan/kickoff.md +73 -0
- package/commands/spartan/kotlin-service.md +109 -0
- package/commands/spartan/lean-canvas.md +222 -0
- package/commands/spartan/lint-rules.md +122 -0
- package/commands/spartan/map-codebase.md +124 -0
- package/commands/spartan/migration.md +82 -0
- package/commands/spartan/next-app.md +317 -0
- package/commands/spartan/next-feature.md +212 -0
- package/commands/spartan/onboard.md +326 -0
- package/commands/spartan/outreach.md +16 -0
- package/commands/spartan/phase.md +142 -0
- package/commands/spartan/pitch.md +18 -0
- package/commands/spartan/plan.md +210 -0
- package/commands/spartan/pr-ready.md +202 -0
- package/commands/spartan/project.md +106 -0
- package/commands/spartan/qa.md +222 -0
- package/commands/spartan/research.md +254 -0
- package/commands/spartan/review.md +132 -0
- package/commands/spartan/scan-rules.md +173 -0
- package/commands/spartan/sessions.md +143 -0
- package/commands/spartan/spec.md +131 -0
- package/commands/spartan/startup.md +257 -0
- package/commands/spartan/team.md +570 -0
- package/commands/spartan/teardown.md +161 -0
- package/commands/spartan/testcontainer.md +97 -0
- package/commands/spartan/tf-cost.md +123 -0
- package/commands/spartan/tf-deploy.md +116 -0
- package/commands/spartan/tf-drift.md +100 -0
- package/commands/spartan/tf-import.md +107 -0
- package/commands/spartan/tf-module.md +121 -0
- package/commands/spartan/tf-plan.md +100 -0
- package/commands/spartan/tf-review.md +106 -0
- package/commands/spartan/tf-scaffold.md +109 -0
- package/commands/spartan/tf-security.md +147 -0
- package/commands/spartan/think.md +221 -0
- package/commands/spartan/unfreeze.md +13 -0
- package/commands/spartan/update.md +134 -0
- package/commands/spartan/ux.md +1233 -0
- package/commands/spartan/validate.md +193 -0
- package/commands/spartan/web-to-prd.md +706 -0
- package/commands/spartan/workstreams.md +109 -0
- package/commands/spartan/write.md +16 -0
- package/commands/spartan.md +386 -0
- package/frameworks/00-framework-comparison-guide.md +317 -0
- package/frameworks/01-lean-canvas.md +196 -0
- package/frameworks/02-design-sprint.md +304 -0
- package/frameworks/03-foundation-sprint.md +337 -0
- package/frameworks/04-business-model-canvas.md +391 -0
- package/frameworks/05-customer-development.md +426 -0
- package/frameworks/06-jobs-to-be-done.md +358 -0
- package/frameworks/07-mom-test.md +392 -0
- package/frameworks/08-value-proposition-canvas.md +488 -0
- package/frameworks/09-javelin-board.md +428 -0
- package/frameworks/10-build-measure-learn.md +467 -0
- package/frameworks/11-mvp-approaches.md +533 -0
- package/frameworks/think-before-build.md +593 -0
- package/lib/assembler.js +197 -0
- package/lib/assembler.test.js +159 -0
- package/lib/detector.js +166 -0
- package/lib/detector.test.js +221 -0
- package/lib/packs.js +16 -0
- package/lib/resolver.js +272 -0
- package/lib/resolver.test.js +298 -0
- package/lib/worktree.sh +104 -0
- package/package.json +50 -0
- package/packs/backend-micronaut.yaml +35 -0
- package/packs/backend-nodejs.yaml +15 -0
- package/packs/backend-python.yaml +15 -0
- package/packs/core.yaml +37 -0
- package/packs/database.yaml +21 -0
- package/packs/frontend-react.yaml +24 -0
- package/packs/infrastructure.yaml +40 -0
- package/packs/ops.yaml +16 -0
- package/packs/packs.compiled.json +371 -0
- package/packs/product.yaml +22 -0
- package/packs/project-mgmt.yaml +24 -0
- package/packs/research.yaml +39 -0
- package/packs/shared-backend.yaml +14 -0
- package/packs/ux-design.yaml +21 -0
- package/rules/backend-micronaut/API_DESIGN.md +313 -0
- package/rules/backend-micronaut/BATCH_PROCESSING.md +92 -0
- package/rules/backend-micronaut/CONTROLLERS.md +388 -0
- package/rules/backend-micronaut/KOTLIN.md +414 -0
- package/rules/backend-micronaut/RETROFIT_PLACEMENT.md +290 -0
- package/rules/backend-micronaut/SERVICES_AND_BEANS.md +325 -0
- package/rules/core/NAMING_CONVENTIONS.md +208 -0
- package/rules/core/SKILL_AUTHORING.md +174 -0
- package/rules/core/TIMEZONE.md +316 -0
- package/rules/database/ORM_AND_REPO.md +289 -0
- package/rules/database/SCHEMA.md +146 -0
- package/rules/database/TRANSACTIONS.md +311 -0
- package/rules/frontend-react/FRONTEND.md +344 -0
- package/rules/infrastructure/MODULES.md +260 -0
- package/rules/infrastructure/NAMING.md +196 -0
- package/rules/infrastructure/PROVIDERS.md +309 -0
- package/rules/infrastructure/SECURITY.md +310 -0
- package/rules/infrastructure/STATE_AND_BACKEND.md +237 -0
- package/rules/infrastructure/STRUCTURE.md +234 -0
- package/rules/infrastructure/VARIABLES.md +285 -0
- package/rules/shared-backend/ARCHITECTURE.md +46 -0
- package/rules/ux-design/DESIGN_PROCESS.md +176 -0
- package/skills/api-endpoint-creator/SKILL.md +455 -0
- package/skills/api-endpoint-creator/error-handling-guide.md +244 -0
- package/skills/api-endpoint-creator/examples.md +522 -0
- package/skills/api-endpoint-creator/testing-patterns.md +302 -0
- package/skills/article-writing/SKILL.md +109 -0
- package/skills/article-writing/examples.md +59 -0
- package/skills/backend-api-design/SKILL.md +84 -0
- package/skills/backend-api-design/code-patterns.md +138 -0
- package/skills/brainstorm/SKILL.md +95 -0
- package/skills/browser-qa/SKILL.md +87 -0
- package/skills/browser-qa/playwright-snippets.md +110 -0
- package/skills/ci-cd-patterns/SKILL.md +108 -0
- package/skills/ci-cd-patterns/workflows.md +149 -0
- package/skills/competitive-teardown/SKILL.md +93 -0
- package/skills/competitive-teardown/example-analysis.md +50 -0
- package/skills/content-engine/SKILL.md +131 -0
- package/skills/content-engine/examples.md +72 -0
- package/skills/database-patterns/SKILL.md +72 -0
- package/skills/database-patterns/code-templates.md +114 -0
- package/skills/database-table-creator/SKILL.md +141 -0
- package/skills/database-table-creator/examples.md +552 -0
- package/skills/database-table-creator/kotlin-templates.md +400 -0
- package/skills/database-table-creator/migration-template.sql +68 -0
- package/skills/database-table-creator/validation-checklist.md +337 -0
- package/skills/deep-research/SKILL.md +80 -0
- package/skills/design-intelligence/SKILL.md +268 -0
- package/skills/design-workflow/SKILL.md +127 -0
- package/skills/design-workflow/checklists.md +45 -0
- package/skills/idea-validation/SKILL.md +129 -0
- package/skills/idea-validation/example-report.md +50 -0
- package/skills/investor-materials/SKILL.md +122 -0
- package/skills/investor-materials/example-outline.md +70 -0
- package/skills/investor-outreach/SKILL.md +112 -0
- package/skills/investor-outreach/examples.md +76 -0
- package/skills/kotlin-best-practices/SKILL.md +58 -0
- package/skills/kotlin-best-practices/code-patterns.md +132 -0
- package/skills/market-research/SKILL.md +99 -0
- package/skills/security-checklist/SKILL.md +65 -0
- package/skills/security-checklist/audit-reference.md +95 -0
- package/skills/service-debugging/SKILL.md +116 -0
- package/skills/service-debugging/common-issues.md +65 -0
- package/skills/startup-pipeline/SKILL.md +152 -0
- package/skills/terraform-best-practices/SKILL.md +244 -0
- package/skills/terraform-module-creator/SKILL.md +284 -0
- package/skills/terraform-review/SKILL.md +222 -0
- package/skills/terraform-security-audit/SKILL.md +280 -0
- package/skills/terraform-service-scaffold/SKILL.md +574 -0
- package/skills/testing-strategies/SKILL.md +116 -0
- package/skills/testing-strategies/examples.md +103 -0
- package/skills/testing-strategies/integration-test-setup.md +71 -0
- package/skills/ui-ux-pro-max/SKILL.md +238 -0
- package/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/skills/ui-ux-pro-max/data/styles.csv +68 -0
- package/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/skills/ui-ux-pro-max/python-setup.md +146 -0
- package/skills/ui-ux-pro-max/scripts/core.py +253 -0
- package/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/skills/ui-ux-pro-max/scripts/search.py +114 -0
- package/skills/web-to-prd/SKILL.md +478 -0
- package/templates/build-config.yaml +44 -0
- package/templates/commands-config.yaml +55 -0
- package/templates/competitor-analysis.md +60 -0
- package/templates/content/AGENT_TEMPLATE.md +47 -0
- package/templates/content/COMMAND_TEMPLATE.md +27 -0
- package/templates/content/RULE_TEMPLATE.md +40 -0
- package/templates/content/SKILL_TEMPLATE.md +41 -0
- package/templates/design-config.md +105 -0
- package/templates/design-doc.md +207 -0
- package/templates/epic.md +100 -0
- package/templates/feature-spec.md +181 -0
- package/templates/idea-canvas.md +47 -0
- package/templates/implementation-plan.md +159 -0
- package/templates/prd-template.md +86 -0
- package/templates/preamble.md +89 -0
- package/templates/project-readme.md +35 -0
- package/templates/quality-gates.md +230 -0
- package/templates/spartan-config.yaml +164 -0
- package/templates/user-interview.md +69 -0
- package/templates/validation-checklist.md +108 -0
- package/templates/workflow-backend-micronaut.md +409 -0
- package/templates/workflow-frontend-react.md +233 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
# Frontend Rules
|
|
2
|
+
|
|
3
|
+
> Full guide: use `/ui-ux-pro-max` skill
|
|
4
|
+
|
|
5
|
+
## Build Check (CRITICAL)
|
|
6
|
+
|
|
7
|
+
Run `yarn build` (or `npm run build`) before committing any `.tsx`/`.ts` changes.
|
|
8
|
+
|
|
9
|
+
### What This Catches
|
|
10
|
+
|
|
11
|
+
- Unused imports (`TS6133`)
|
|
12
|
+
- Unused variables and functions (`TS6133`)
|
|
13
|
+
- Type errors
|
|
14
|
+
- Missing exports
|
|
15
|
+
- Wrong function signatures
|
|
16
|
+
|
|
17
|
+
### Common Mistakes: Leftover imports/variables after refactoring
|
|
18
|
+
|
|
19
|
+
When you remove JSX that uses a component, state, or handler, also remove:
|
|
20
|
+
1. The import statement
|
|
21
|
+
2. The state declaration (`useState`)
|
|
22
|
+
3. The handler function
|
|
23
|
+
4. Any type imports only used by removed code
|
|
24
|
+
|
|
25
|
+
**Example**: If you remove a modal from JSX, also remove:
|
|
26
|
+
- The `showModal` state
|
|
27
|
+
- The `handleOpenModal` / `handleCloseModal` handlers
|
|
28
|
+
- Any state only used inside that modal
|
|
29
|
+
- Component imports only used in that modal
|
|
30
|
+
|
|
31
|
+
### When to Run
|
|
32
|
+
|
|
33
|
+
- After ANY edit to `.tsx` or `.ts` files
|
|
34
|
+
- Before staging files for commit
|
|
35
|
+
- If build fails, fix ALL errors before committing
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## API Client Case Conversion (CRITICAL)
|
|
40
|
+
|
|
41
|
+
**Backend uses `snake_case`, frontend uses `camelCase`**. Always convert between them.
|
|
42
|
+
|
|
43
|
+
### When creating/modifying API client files (`src/api/*.ts`)
|
|
44
|
+
|
|
45
|
+
**Always**:
|
|
46
|
+
```typescript
|
|
47
|
+
import { keysToSnake, keysToCamel } from '@/utils/caseConvert'
|
|
48
|
+
|
|
49
|
+
export const apiClient = {
|
|
50
|
+
// CORRECT: Convert request to snake_case before sending
|
|
51
|
+
async createResource(request: CreateRequestType): Promise<ResponseType> {
|
|
52
|
+
const response = await httpClient.post(`${BASE}/resource/create`, keysToSnake(request))
|
|
53
|
+
return keysToCamel(response.data) // CORRECT: Convert response from snake_case
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
// WRONG: Not converting
|
|
57
|
+
async wrongCreateResource(request: CreateRequestType): Promise<ResponseType> {
|
|
58
|
+
const response = await httpClient.post(`${BASE}/resource/create`, request)
|
|
59
|
+
return response.data
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Why This Matters
|
|
65
|
+
|
|
66
|
+
Backend expects `{ "machine_box_id": "..." }` but frontend sends `{ "machineBoxId": "..." }`:
|
|
67
|
+
- **Result**: Micronaut JSON deserializer fails with "parameter is null" error
|
|
68
|
+
- **Root cause**: Field name mismatch (`machineBoxId` != `machine_box_id`)
|
|
69
|
+
|
|
70
|
+
### Common Mistakes
|
|
71
|
+
|
|
72
|
+
1. **Missing `keysToSnake` import** - Request fields don't match backend
|
|
73
|
+
2. **Missing `keysToCamel` on responses** - Response fields don't match TypeScript types
|
|
74
|
+
3. **Partial conversion** - Some methods converted, others not (inconsistent)
|
|
75
|
+
|
|
76
|
+
### Reference Example
|
|
77
|
+
|
|
78
|
+
See your API client files for correct pattern.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## E2E Patterns
|
|
83
|
+
|
|
84
|
+
### Selector Ambiguity Handling
|
|
85
|
+
|
|
86
|
+
**Use `.first()` when selectors might match multiple elements**.
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
// CORRECT: Use .first() when multiple matches possible
|
|
90
|
+
await expect(page.getByRole('table').or(page.getByText('No data found')).first()).toBeVisible();
|
|
91
|
+
|
|
92
|
+
// CORRECT: Explicit for single elements
|
|
93
|
+
await expect(page.getByRole('heading', { name: 'Settings' })).toBeVisible();
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Playwright's `or()` and `getByText()` can match multiple elements:
|
|
97
|
+
- **Without `.first()`**: "Ambiguous element locator" or timeout
|
|
98
|
+
- **With `.first()`**: Explicitly chooses first match
|
|
99
|
+
|
|
100
|
+
Common scenarios:
|
|
101
|
+
1. **Empty states**: Table or "No data found" message
|
|
102
|
+
2. **Dynamic content**: Multiple elements with similar text
|
|
103
|
+
3. **Loading states**: Spinner vs loaded content
|
|
104
|
+
|
|
105
|
+
### Form State Synchronization
|
|
106
|
+
|
|
107
|
+
**Always sync form state after successful save operations**.
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
const handleSave = async (values: FormValues) => {
|
|
111
|
+
const updated = await apiClient.updateResource(values)
|
|
112
|
+
|
|
113
|
+
// CORRECT: Sync form state with response
|
|
114
|
+
setForm({
|
|
115
|
+
...updated,
|
|
116
|
+
// Ensure all fields are mapped correctly
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
toast.success('Saved successfully')
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// WRONG: Not syncing state, form shows old values
|
|
123
|
+
const wrongHandleSave = async (values: FormValues) => {
|
|
124
|
+
await apiClient.updateResource(values)
|
|
125
|
+
toast.success('Saved successfully')
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
After save, API returns updated data (with timestamps, computed fields, defaults):
|
|
130
|
+
- **Without sync**: Form shows stale values, user thinks save failed
|
|
131
|
+
- **With sync**: Form shows fresh data from API, user sees changes
|
|
132
|
+
|
|
133
|
+
Common issues:
|
|
134
|
+
1. **Settings not persisting**: Form submit succeeds but UI shows old values
|
|
135
|
+
2. **Data not refreshing**: Add/edit operations complete but list doesn't update
|
|
136
|
+
3. **Navigation doesn't refetch**: Moving between tabs doesn't trigger data reload
|
|
137
|
+
|
|
138
|
+
### E2E Test File Structure
|
|
139
|
+
|
|
140
|
+
Follow consistent structure for Playwright test files.
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import { test, expect } from '@playwright/test';
|
|
144
|
+
import { screenshotDir } from '../fixtures/paths';
|
|
145
|
+
|
|
146
|
+
const SCREENSHOT_DIR = screenshotDir('feature-name');
|
|
147
|
+
|
|
148
|
+
// --- Page Load & Navigation ---
|
|
149
|
+
|
|
150
|
+
test('feature-name - page loads with heading and tabs', async ({ page }) => {
|
|
151
|
+
await page.goto('/feature-page');
|
|
152
|
+
await page.waitForLoadState('networkidle', { timeout: 10000 });
|
|
153
|
+
|
|
154
|
+
// Page heading should be visible
|
|
155
|
+
await expect(page.getByRole('heading', { name: 'Feature Name' })).toBeVisible();
|
|
156
|
+
|
|
157
|
+
// Tabs should be visible
|
|
158
|
+
await expect(page.getByRole('tab')).toBeVisible();
|
|
159
|
+
|
|
160
|
+
await page.screenshot({ path: `${SCREENSHOT_DIR}/page-loaded.png`, fullPage: true });
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// --- CRUD Operations ---
|
|
164
|
+
|
|
165
|
+
test('feature-name - create item and verify', async ({ page }) => {
|
|
166
|
+
await page.goto('/feature-page');
|
|
167
|
+
await page.waitForLoadState('networkidle', { timeout: 10000 });
|
|
168
|
+
|
|
169
|
+
// Click "Create" button
|
|
170
|
+
await page.getByRole('button', { name: 'Create Item' }).click();
|
|
171
|
+
|
|
172
|
+
// Fill form
|
|
173
|
+
await page.getByRole('textbox', { name: 'Name' }).fill('Test Item');
|
|
174
|
+
|
|
175
|
+
// Submit
|
|
176
|
+
await page.getByRole('button', { name: 'Save' }).click();
|
|
177
|
+
await page.waitForLoadState('networkidle', { timeout: 5000 });
|
|
178
|
+
|
|
179
|
+
// Verify item appears in list
|
|
180
|
+
await expect(page.getByText('Test Item')).toBeVisible({ timeout: 5000 });
|
|
181
|
+
|
|
182
|
+
await page.screenshot({ path: `${SCREENSHOT_DIR}/create-item.png`, fullPage: true });
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Naming Conventions
|
|
187
|
+
|
|
188
|
+
- **Test names**: `feature-name - what it tests` (lowercase, descriptive)
|
|
189
|
+
- **Screenshot paths**: Use `screenshotDir()` helper for consistent paths
|
|
190
|
+
- **Timeouts**: 10s for page load, 5s for operations
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Checklist: Before Running E2E Tests
|
|
195
|
+
|
|
196
|
+
- [ ] Backend running and connected to database
|
|
197
|
+
- [ ] All POST requests in API clients use `keysToSnake()`
|
|
198
|
+
- [ ] All response returns use `keysToCamel()`
|
|
199
|
+
- [ ] Form submissions sync state after successful save
|
|
200
|
+
- [ ] Selectors use `.first()` where ambiguity possible
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Related Files
|
|
205
|
+
|
|
206
|
+
- API client files: `src/api/*.ts`
|
|
207
|
+
- Case conversion utility: `src/utils/caseConvert.ts`
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## API Response Null Safety (merged from FRONTEND_CODING_STANDARDS.md)
|
|
212
|
+
|
|
213
|
+
### ALWAYS Use Optional Chaining + Nullish Coalescing for API Responses
|
|
214
|
+
|
|
215
|
+
API responses may return `undefined` or have missing properties. NEVER access response properties without null safety.
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
// ✅ CORRECT — safe with fallback
|
|
219
|
+
const response = await teamApi.list(projectId)
|
|
220
|
+
setTeams(response?.teams ?? [])
|
|
221
|
+
|
|
222
|
+
// ❌ WRONG — will crash if response is undefined
|
|
223
|
+
const response = await teamApi.list(projectId)
|
|
224
|
+
setTeams(response.teams)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Catch Blocks MUST Reset State to Safe Defaults
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
// ✅ CORRECT
|
|
231
|
+
const fetchTeams = useCallback(async () => {
|
|
232
|
+
try {
|
|
233
|
+
const response = await teamApi.list(projectId)
|
|
234
|
+
setTeams(response?.teams ?? [])
|
|
235
|
+
} catch {
|
|
236
|
+
setTeams([]) // Reset to safe default
|
|
237
|
+
}
|
|
238
|
+
}, [projectId])
|
|
239
|
+
|
|
240
|
+
// ❌ WRONG — state left stale on error
|
|
241
|
+
const fetchTeams = useCallback(async () => {
|
|
242
|
+
try {
|
|
243
|
+
const response = await teamApi.list(projectId)
|
|
244
|
+
setTeams(response.teams)
|
|
245
|
+
} catch (e) {
|
|
246
|
+
console.error(e) // State not reset!
|
|
247
|
+
}
|
|
248
|
+
}, [projectId])
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Loading States
|
|
252
|
+
- Always show loading indicators during async operations
|
|
253
|
+
- Disable form submit buttons while requests are in-flight
|
|
254
|
+
- Use skeleton UIs for initial data loads
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Optimistic Updates Pattern
|
|
259
|
+
|
|
260
|
+
### Avoid UI Flash/Reload After Mutations
|
|
261
|
+
|
|
262
|
+
**Any mutation (create, update, delete) should use optimistic updates instead of full query invalidation.**
|
|
263
|
+
|
|
264
|
+
#### Why This Is Critical
|
|
265
|
+
- Prevents jarring UI flash/white screen during refetch
|
|
266
|
+
- Provides instant feedback to users
|
|
267
|
+
- Better perceived performance
|
|
268
|
+
|
|
269
|
+
#### Bad Pattern (UI Flash)
|
|
270
|
+
```typescript
|
|
271
|
+
// BAD - Triggers full refetch, causing flash
|
|
272
|
+
const mutation = useMutation({
|
|
273
|
+
mutationFn: (data) => api.create(data),
|
|
274
|
+
onSuccess: () => {
|
|
275
|
+
queryClient.invalidateQueries({ queryKey: ['feed'] }) // Flash!
|
|
276
|
+
},
|
|
277
|
+
})
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
#### Good Pattern (Optimistic Update)
|
|
281
|
+
```typescript
|
|
282
|
+
// GOOD - Updates cache directly, no flash
|
|
283
|
+
const mutation = useMutation({
|
|
284
|
+
mutationFn: (data) => api.create(data),
|
|
285
|
+
onSuccess: (newItem) => {
|
|
286
|
+
// Update cache directly by prepending new item
|
|
287
|
+
queryClient.setQueryData(['feed'], (oldData: any) => {
|
|
288
|
+
if (!oldData?.pages) return oldData
|
|
289
|
+
return {
|
|
290
|
+
...oldData,
|
|
291
|
+
pages: oldData.pages.map((page: any, index: number) => {
|
|
292
|
+
if (index === 0) {
|
|
293
|
+
return { ...page, items: [newItem, ...(page.items || [])] }
|
|
294
|
+
}
|
|
295
|
+
return page
|
|
296
|
+
}),
|
|
297
|
+
}
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
// Invalidate related queries silently (no immediate refetch)
|
|
301
|
+
queryClient.invalidateQueries({ queryKey: ['related'], refetchType: 'none' })
|
|
302
|
+
},
|
|
303
|
+
})
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Use placeholderData for Query Stability
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
// GOOD - Keep previous data while refetching
|
|
310
|
+
const { data } = useInfiniteQuery({
|
|
311
|
+
queryKey: ['feed'],
|
|
312
|
+
queryFn: fetchFeed,
|
|
313
|
+
placeholderData: (previousData) => previousData, // No flash on refetch
|
|
314
|
+
})
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Optimistic Delete with Rollback
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
const deleteMutation = useMutation({
|
|
321
|
+
mutationFn: (id: string) => api.delete(id),
|
|
322
|
+
onMutate: async (id) => {
|
|
323
|
+
// Cancel outgoing refetches
|
|
324
|
+
await queryClient.cancelQueries({ queryKey: ['items'] })
|
|
325
|
+
|
|
326
|
+
// Save current state for rollback
|
|
327
|
+
const previousData = queryClient.getQueryData(['items'])
|
|
328
|
+
|
|
329
|
+
// Optimistically remove item
|
|
330
|
+
queryClient.setQueryData(['items'], (old: any) => ({
|
|
331
|
+
...old,
|
|
332
|
+
items: old.items.filter((item: any) => item.id !== id),
|
|
333
|
+
}))
|
|
334
|
+
|
|
335
|
+
return { previousData }
|
|
336
|
+
},
|
|
337
|
+
onError: (_err, _id, context) => {
|
|
338
|
+
// Rollback on error
|
|
339
|
+
if (context?.previousData) {
|
|
340
|
+
queryClient.setQueryData(['items'], context.previousData)
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
})
|
|
344
|
+
```
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.tf"
|
|
4
|
+
- "**/*.hcl"
|
|
5
|
+
- "**/*.tfvars"
|
|
6
|
+
---
|
|
7
|
+
# Module Design and Composition
|
|
8
|
+
|
|
9
|
+
> Full guide: use `/spartan:tf-module` command
|
|
10
|
+
|
|
11
|
+
## Registry Modules
|
|
12
|
+
|
|
13
|
+
Use c0x12c registry modules with pessimistic version pinning. Always pin to patch level.
|
|
14
|
+
|
|
15
|
+
```hcl
|
|
16
|
+
module "rds" {
|
|
17
|
+
source = "c0x12c/rds/aws"
|
|
18
|
+
version = "~> 0.6.6"
|
|
19
|
+
|
|
20
|
+
identifier = var.rds_identifier
|
|
21
|
+
instance_class = var.rds_instance_class
|
|
22
|
+
subnet_ids = var.private_subnet_ids
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### WRONG -- Unbounded version constraint
|
|
27
|
+
|
|
28
|
+
```hcl
|
|
29
|
+
module "rds" {
|
|
30
|
+
source = "c0x12c/rds/aws"
|
|
31
|
+
version = ">= 0.6.0" # Could pull breaking changes
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### WRONG -- No version constraint
|
|
36
|
+
|
|
37
|
+
```hcl
|
|
38
|
+
module "rds" {
|
|
39
|
+
source = "c0x12c/rds/aws"
|
|
40
|
+
# No version -- pulls latest, unpredictable
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### CORRECT -- Patch-level pessimistic pinning
|
|
45
|
+
|
|
46
|
+
```hcl
|
|
47
|
+
module "rds" {
|
|
48
|
+
source = "c0x12c/rds/aws"
|
|
49
|
+
version = "~> 0.6.6" # Allows 0.6.x, blocks 0.7.0+
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Local Modules
|
|
56
|
+
|
|
57
|
+
Service-specific modules live in `modules/{service}/` with resource-per-file pattern.
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
modules/
|
|
61
|
+
└── {service}/
|
|
62
|
+
├── variables.tf # All inputs declared
|
|
63
|
+
├── outputs.tf # All outputs declared
|
|
64
|
+
├── locals.tf # Computed values
|
|
65
|
+
├── ecr.tf # ECR repositories
|
|
66
|
+
├── rds.tf # RDS PostgreSQL
|
|
67
|
+
├── redis.tf # ElastiCache Redis
|
|
68
|
+
├── s3.tf # S3 buckets
|
|
69
|
+
├── eks.tf # K8s namespace, IRSA, secrets, configmaps
|
|
70
|
+
└── sqs.tf # SQS queues
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### WRONG -- Multiple resource types in one file
|
|
74
|
+
|
|
75
|
+
```hcl
|
|
76
|
+
# resources.tf -- mixing ECR, RDS, and Redis
|
|
77
|
+
module "ecr" {
|
|
78
|
+
source = "c0x12c/ecr/aws"
|
|
79
|
+
version = "~> 0.1.0"
|
|
80
|
+
name = var.service_name
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module "rds" {
|
|
84
|
+
source = "c0x12c/rds/aws"
|
|
85
|
+
version = "~> 0.6.6"
|
|
86
|
+
# ...
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module "redis" {
|
|
90
|
+
source = "c0x12c/redis/aws"
|
|
91
|
+
version = "~> 0.2.0"
|
|
92
|
+
# ...
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### CORRECT -- One resource type per file
|
|
97
|
+
|
|
98
|
+
```hcl
|
|
99
|
+
# ecr.tf
|
|
100
|
+
module "ecr" {
|
|
101
|
+
source = "c0x12c/ecr/aws"
|
|
102
|
+
version = "~> 0.1.0"
|
|
103
|
+
name = var.service_name
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
# rds.tf
|
|
107
|
+
module "rds" {
|
|
108
|
+
source = "c0x12c/rds/aws"
|
|
109
|
+
version = "~> 0.6.6"
|
|
110
|
+
# ...
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
# redis.tf
|
|
114
|
+
module "redis" {
|
|
115
|
+
source = "c0x12c/redis/aws"
|
|
116
|
+
version = "~> 0.2.0"
|
|
117
|
+
# ...
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Module Interface Rules
|
|
124
|
+
|
|
125
|
+
Every module must have explicit `variables.tf` and `outputs.tf`. No hardcoded values.
|
|
126
|
+
|
|
127
|
+
### WRONG -- Hardcoded values in module
|
|
128
|
+
|
|
129
|
+
```hcl
|
|
130
|
+
# modules/{service}/rds.tf
|
|
131
|
+
module "rds" {
|
|
132
|
+
source = "c0x12c/rds/aws"
|
|
133
|
+
version = "~> 0.6.6"
|
|
134
|
+
instance_class = "db.t3.micro" # Hardcoded
|
|
135
|
+
subnet_ids = ["subnet-abc123"] # Hardcoded
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### CORRECT -- Parameterized module
|
|
140
|
+
|
|
141
|
+
```hcl
|
|
142
|
+
# modules/{service}/variables.tf
|
|
143
|
+
variable "rds_instance_class" {
|
|
144
|
+
description = "RDS instance class"
|
|
145
|
+
type = string
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
variable "private_subnet_ids" {
|
|
149
|
+
description = "Private subnet IDs for data stores"
|
|
150
|
+
type = list(string)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
# modules/{service}/rds.tf
|
|
154
|
+
module "rds" {
|
|
155
|
+
source = "c0x12c/rds/aws"
|
|
156
|
+
version = "~> 0.6.6"
|
|
157
|
+
instance_class = var.rds_instance_class
|
|
158
|
+
subnet_ids = var.private_subnet_ids
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
# modules/{service}/outputs.tf
|
|
162
|
+
output "rds_endpoint" {
|
|
163
|
+
description = "RDS instance endpoint"
|
|
164
|
+
value = module.rds.endpoint
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
output "rds_password" {
|
|
168
|
+
description = "RDS auto-generated password"
|
|
169
|
+
value = module.rds.password
|
|
170
|
+
sensitive = true
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Sensitive for_each Gotcha
|
|
177
|
+
|
|
178
|
+
Outputs marked `sensitive = true` in source modules break `for_each` in consumer modules. Terraform cannot use sensitive values as map keys.
|
|
179
|
+
|
|
180
|
+
### WRONG -- Trying to iterate over sensitive output
|
|
181
|
+
|
|
182
|
+
```hcl
|
|
183
|
+
# This fails at plan time
|
|
184
|
+
resource "kubernetes_secret" "db_creds" {
|
|
185
|
+
for_each = module.rds.connection_map # Error: sensitive value in for_each
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### CORRECT -- Fix at source module or use nonsensitive()
|
|
190
|
+
|
|
191
|
+
```hcl
|
|
192
|
+
# Option 1: Fix the source module output to not be sensitive
|
|
193
|
+
output "connection_map" {
|
|
194
|
+
value = { for k, v in local.connections : k => v }
|
|
195
|
+
# Remove sensitive = true if the map keys are not sensitive
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
# Option 2: Use nonsensitive() on non-secret parts only
|
|
199
|
+
resource "kubernetes_secret" "db_creds" {
|
|
200
|
+
for_each = nonsensitive(module.rds.connection_names)
|
|
201
|
+
|
|
202
|
+
metadata {
|
|
203
|
+
name = each.value
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Shared Modules Pattern
|
|
211
|
+
|
|
212
|
+
In multi-root setups, use `live/shared/` for cross-environment reusable compositions.
|
|
213
|
+
|
|
214
|
+
```
|
|
215
|
+
live/
|
|
216
|
+
├── shared/
|
|
217
|
+
│ ├── ecr/
|
|
218
|
+
│ │ ├── variables.tf
|
|
219
|
+
│ │ ├── outputs.tf
|
|
220
|
+
│ │ └── main.tf
|
|
221
|
+
│ └── rds/
|
|
222
|
+
│ ├── variables.tf
|
|
223
|
+
│ ├── outputs.tf
|
|
224
|
+
│ └── main.tf
|
|
225
|
+
├── dev/
|
|
226
|
+
│ └── main.tf # Calls shared modules with dev params
|
|
227
|
+
└── prod/
|
|
228
|
+
└── main.tf # Calls shared modules with prod params
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
```hcl
|
|
232
|
+
# live/dev/main.tf
|
|
233
|
+
module "rds" {
|
|
234
|
+
source = "../shared/rds"
|
|
235
|
+
environment = "dev"
|
|
236
|
+
instance_class = "db.t3.micro"
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
# live/prod/main.tf
|
|
240
|
+
module "rds" {
|
|
241
|
+
source = "../shared/rds"
|
|
242
|
+
environment = "prod"
|
|
243
|
+
instance_class = "db.r6g.large"
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Quick Reference
|
|
250
|
+
|
|
251
|
+
| Aspect | Rule |
|
|
252
|
+
|--------|------|
|
|
253
|
+
| Registry modules | c0x12c registry with `~> X.Y.Z` pinning |
|
|
254
|
+
| Version constraint | Pessimistic (`~>`) at patch level, never unbounded (`>=`) |
|
|
255
|
+
| Local modules | `modules/{service}/` with resource-per-file |
|
|
256
|
+
| File pattern | One resource type per `.tf` file |
|
|
257
|
+
| Module interface | Explicit `variables.tf` + `outputs.tf`, no hardcoded values |
|
|
258
|
+
| Sensitive for_each | Fix at source module, not consumer |
|
|
259
|
+
| Shared modules | `live/shared/` for multi-root cross-environment reuse |
|
|
260
|
+
| Variable declarations | Declare all variables, even if sourced from remote state |
|