chore: update .gitignore and improve coding standards documentation
- Added .cursor/sessions/* to .gitignore to prevent session files from being tracked. - Enhanced coding standards in SKILL.md by adding semicolons to TypeScript examples for consistency. - Improved formatting in continuous learning, detail layout, and other SKILL.md files for better readability. These changes aim to streamline development processes and maintain code quality across the project.
This commit is contained in:
@@ -37,8 +37,8 @@ Standards for this TypeScript/React frontend. Not a NestJS API.
|
|||||||
### Naming
|
### Naming
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
const searchQuery = 'widget'
|
const searchQuery = 'widget';
|
||||||
const isAuthenticated = true
|
const isAuthenticated = true;
|
||||||
|
|
||||||
async function fetchVehicleType(id: string) {}
|
async function fetchVehicleType(id: string) {}
|
||||||
function isValidCode(code: string): boolean {}
|
function isValidCode(code: string): boolean {}
|
||||||
@@ -47,8 +47,8 @@ function isValidCode(code: string): boolean {}
|
|||||||
### Immutability (CRITICAL)
|
### Immutability (CRITICAL)
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
const updated = { ...row, name: 'New' }
|
const updated = { ...row, name: 'New' };
|
||||||
const nextItems = [...items, newItem]
|
const nextItems = [...items, newItem];
|
||||||
```
|
```
|
||||||
|
|
||||||
Never mutate: no `push`, `splice`, or in-place property assignment on shared state.
|
Never mutate: no `push`, `splice`, or in-place property assignment on shared state.
|
||||||
@@ -71,12 +71,12 @@ No `any`. Prefer entity types in `domain/entities` and DTOs next to transformers
|
|||||||
## Imports (this repo)
|
## Imports (this repo)
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { Button, Text } from '@repo/ui/components'
|
import { Button, Text } from '@repo/ui/components';
|
||||||
import { FieldTextInput } from '@repo/ui/form'
|
import { FieldTextInput } from '@repo/ui/form';
|
||||||
import { EnterpriseModuleProvider } from '@repo/ui/foundations'
|
import { EnterpriseModuleProvider } from '@repo/ui/foundations';
|
||||||
import { compose, required } from '@repo/ui/validators'
|
import { compose, required } from '@repo/ui/validators';
|
||||||
import { createHttpClient } from '@repo/core-api/http-client'
|
import { createHttpClient } from '@repo/core-api/http-client';
|
||||||
import { CommonRemoteDataServices } from '@repo/core-api/data-services'
|
import { CommonRemoteDataServices } from '@repo/core-api/data-services';
|
||||||
```
|
```
|
||||||
|
|
||||||
Do not import `@mantine/core` or axios in `apps/web` feature code.
|
Do not import `@mantine/core` or axios in `apps/web` feature code.
|
||||||
|
|||||||
@@ -32,18 +32,14 @@ Edit `config.json` to customize:
|
|||||||
"debugging_techniques",
|
"debugging_techniques",
|
||||||
"project_specific"
|
"project_specific"
|
||||||
],
|
],
|
||||||
"ignore_patterns": [
|
"ignore_patterns": ["simple_typos", "one_time_fixes", "external_api_issues"]
|
||||||
"simple_typos",
|
|
||||||
"one_time_fixes",
|
|
||||||
"external_api_issues"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Pattern Types
|
## Pattern Types
|
||||||
|
|
||||||
| Pattern | Description |
|
| Pattern | Description |
|
||||||
|---------|-------------|
|
| ---------------------- | ------------------------------------- |
|
||||||
| `error_resolution` | How specific errors were resolved |
|
| `error_resolution` | How specific errors were resolved |
|
||||||
| `user_corrections` | Patterns from user corrections |
|
| `user_corrections` | Patterns from user corrections |
|
||||||
| `workarounds` | Solutions to framework/library quirks |
|
| `workarounds` | Solutions to framework/library quirks |
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ Optional **identity / hero** (§3) is an extra content block when the entity ben
|
|||||||
Look at **how many distinct data categories** the entity has — that decides stacked vs tabbed body:
|
Look at **how many distinct data categories** the entity has — that decides stacked vs tabbed body:
|
||||||
|
|
||||||
| Detail type | Body mode |
|
| Detail type | Body mode |
|
||||||
| --- | --- |
|
| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
| Few categories (≤3–4 section cards; e.g. Branch, User, simple master data) | **Stacked sections** — vertical stack of section cards under the header |
|
| Few categories (≤3–4 section cards; e.g. Branch, User, simple master data) | **Stacked sections** — vertical stack of section cards under the header |
|
||||||
| Many categories (>3–4 distinct groups) **or** the user explicitly asks for tabs | **Tabbed body** — keep a slim always-visible top (identity / summary / stepper), then a horizontal tab bar; each tab owns one category's content |
|
| Many categories (>3–4 distinct groups) **or** the user explicitly asks for tabs | **Tabbed body** — keep a slim always-visible top (identity / summary / stepper), then a horizontal tab bar; each tab owns one category's content |
|
||||||
| Narrow / mobile viewport | Same mode as desktop; only the **internal** key-value and status layouts collapse (§13) |
|
| Narrow / mobile viewport | Same mode as desktop; only the **internal** key-value and status layouts collapse (§13) |
|
||||||
@@ -68,7 +68,7 @@ Use when the entity has a meaningful visual (device photo, avatar, logo). Skip f
|
|||||||
Single horizontal row inside one section surface:
|
Single horizontal row inside one section surface:
|
||||||
|
|
||||||
| Left | Middle (flex grow) | Right |
|
| Left | Middle (flex grow) | Right |
|
||||||
| --- | --- | --- |
|
| ----------------------------------- | ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- |
|
||||||
| Leading media (fixed square/circle) | Title stack: primary name (+ id if not already in chrome), then one short subtitle/category line | Primary content action if it belongs to this block (usually omit — Edit lives in module chrome) |
|
| Leading media (fixed square/circle) | Title stack: primary name (+ id if not already in chrome), then one short subtitle/category line | Primary content action if it belongs to this block (usually omit — Edit lives in module chrome) |
|
||||||
|
|
||||||
- Media left-aligned; title stack left-aligned next to media; any block-level action right-aligned on the same row.
|
- Media left-aligned; title stack left-aligned next to media; any block-level action right-aligned on the same row.
|
||||||
@@ -204,7 +204,7 @@ Horizontal gaps inside grids match the medium rhythm. Don't invent a fourth gap
|
|||||||
## 13. Responsive collapse
|
## 13. Responsive collapse
|
||||||
|
|
||||||
| Wide layout | Narrow collapse |
|
| Wide layout | Narrow collapse |
|
||||||
| --- | --- |
|
| -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| Identity hero row (media \| title \| action) | Centered vertical stack (media → title → subtitle → full-width action) |
|
| Identity hero row (media \| title \| action) | Centered vertical stack (media → title → subtitle → full-width action) |
|
||||||
| Key-value label-above grid (§5A) | Label-left / value-right rows (§5B) |
|
| Key-value label-above grid (§5A) | Label-left / value-right rows (§5B) |
|
||||||
| Status metric horizontal row (§6) | Stacked metric sub-blocks; badge beside value |
|
| Status metric horizontal row (§6) | Stacked metric sub-blocks; badge beside value |
|
||||||
@@ -217,7 +217,7 @@ Never re-pair fields into different logical groups on collapse — only column c
|
|||||||
## 14. Action placement (position only)
|
## 14. Action placement (position only)
|
||||||
|
|
||||||
| Action scope | Position |
|
| Action scope | Position |
|
||||||
| --- | --- |
|
| ------------------------------------------------- | ----------------------------------------------------------------------------------- |
|
||||||
| Global entity actions (Edit, Delete, Activate, …) | Module header actions via `EnterpriseDetailPageProvider` — top-right of chrome |
|
| Global entity actions (Edit, Delete, Activate, …) | Module header actions via `EnterpriseDetailPageProvider` — top-right of chrome |
|
||||||
| Section-scoped action (Upload, Add record) | Right side of that section's header band (§4 / §11) |
|
| Section-scoped action (Upload, Add record) | Right side of that section's header band (§4 / §11) |
|
||||||
| Section filters | Right side of the table/section header, before or beside the section primary action |
|
| Section filters | Right side of the table/section header, before or beside the section primary action |
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ A formal evaluation framework for Claude Code sessions, implementing eval-driven
|
|||||||
## Philosophy
|
## Philosophy
|
||||||
|
|
||||||
Eval-Driven Development treats evals as the "unit tests of AI development":
|
Eval-Driven Development treats evals as the "unit tests of AI development":
|
||||||
|
|
||||||
- Define expected behavior BEFORE implementation
|
- Define expected behavior BEFORE implementation
|
||||||
- Run evals continuously during development
|
- Run evals continuously during development
|
||||||
- Track regressions with each change
|
- Track regressions with each change
|
||||||
@@ -13,11 +14,14 @@ Eval-Driven Development treats evals as the "unit tests of AI development":
|
|||||||
## Eval Types
|
## Eval Types
|
||||||
|
|
||||||
### Capability Evals
|
### Capability Evals
|
||||||
|
|
||||||
Test if Claude can do something it couldn't before:
|
Test if Claude can do something it couldn't before:
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
[CAPABILITY EVAL: feature-name]
|
[CAPABILITY EVAL: feature-name]
|
||||||
Task: Description of what Claude should accomplish
|
Task: Description of what Claude should accomplish
|
||||||
Success Criteria:
|
Success Criteria:
|
||||||
|
|
||||||
- [ ] Criterion 1
|
- [ ] Criterion 1
|
||||||
- [ ] Criterion 2
|
- [ ] Criterion 2
|
||||||
- [ ] Criterion 3
|
- [ ] Criterion 3
|
||||||
@@ -25,11 +29,14 @@ Expected Output: Description of expected result
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Regression Evals
|
### Regression Evals
|
||||||
|
|
||||||
Ensure changes don't break existing functionality:
|
Ensure changes don't break existing functionality:
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
[REGRESSION EVAL: feature-name]
|
[REGRESSION EVAL: feature-name]
|
||||||
Baseline: SHA or checkpoint name
|
Baseline: SHA or checkpoint name
|
||||||
Tests:
|
Tests:
|
||||||
|
|
||||||
- existing-test-1: PASS/FAIL
|
- existing-test-1: PASS/FAIL
|
||||||
- existing-test-2: PASS/FAIL
|
- existing-test-2: PASS/FAIL
|
||||||
- existing-test-3: PASS/FAIL
|
- existing-test-3: PASS/FAIL
|
||||||
@@ -39,7 +46,9 @@ Result: X/Y passed (previously Y/Y)
|
|||||||
## Grader Types
|
## Grader Types
|
||||||
|
|
||||||
### 1. Code-Based Grader
|
### 1. Code-Based Grader
|
||||||
|
|
||||||
Deterministic checks using code:
|
Deterministic checks using code:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check if file contains expected pattern
|
# Check if file contains expected pattern
|
||||||
grep -q "export function handleAuth" src/auth.ts && echo "PASS" || echo "FAIL"
|
grep -q "export function handleAuth" src/auth.ts && echo "PASS" || echo "FAIL"
|
||||||
@@ -52,10 +61,13 @@ npm run build && echo "PASS" || echo "FAIL"
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 2. Model-Based Grader
|
### 2. Model-Based Grader
|
||||||
|
|
||||||
Use Claude to evaluate open-ended outputs:
|
Use Claude to evaluate open-ended outputs:
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
[MODEL GRADER PROMPT]
|
[MODEL GRADER PROMPT]
|
||||||
Evaluate the following code change:
|
Evaluate the following code change:
|
||||||
|
|
||||||
1. Does it solve the stated problem?
|
1. Does it solve the stated problem?
|
||||||
2. Is it well-structured?
|
2. Is it well-structured?
|
||||||
3. Are edge cases handled?
|
3. Are edge cases handled?
|
||||||
@@ -66,7 +78,9 @@ Reasoning: [explanation]
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 3. Human Grader
|
### 3. Human Grader
|
||||||
|
|
||||||
Flag for manual review:
|
Flag for manual review:
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
[HUMAN REVIEW REQUIRED]
|
[HUMAN REVIEW REQUIRED]
|
||||||
Change: Description of what changed
|
Change: Description of what changed
|
||||||
@@ -77,13 +91,17 @@ Risk Level: LOW/MEDIUM/HIGH
|
|||||||
## Metrics
|
## Metrics
|
||||||
|
|
||||||
### pass@k
|
### pass@k
|
||||||
|
|
||||||
"At least one success in k attempts"
|
"At least one success in k attempts"
|
||||||
|
|
||||||
- pass@1: First attempt success rate
|
- pass@1: First attempt success rate
|
||||||
- pass@3: Success within 3 attempts
|
- pass@3: Success within 3 attempts
|
||||||
- Typical target: pass@3 > 90%
|
- Typical target: pass@3 > 90%
|
||||||
|
|
||||||
### pass^k
|
### pass^k
|
||||||
|
|
||||||
"All k trials succeed"
|
"All k trials succeed"
|
||||||
|
|
||||||
- Higher bar for reliability
|
- Higher bar for reliability
|
||||||
- pass^3: 3 consecutive successes
|
- pass^3: 3 consecutive successes
|
||||||
- Use for critical paths
|
- Use for critical paths
|
||||||
@@ -91,28 +109,34 @@ Risk Level: LOW/MEDIUM/HIGH
|
|||||||
## Eval Workflow
|
## Eval Workflow
|
||||||
|
|
||||||
### 1. Define (Before Coding)
|
### 1. Define (Before Coding)
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
## EVAL DEFINITION: feature-xyz
|
## EVAL DEFINITION: feature-xyz
|
||||||
|
|
||||||
### Capability Evals
|
### Capability Evals
|
||||||
|
|
||||||
1. Can create new user account
|
1. Can create new user account
|
||||||
2. Can validate email format
|
2. Can validate email format
|
||||||
3. Can hash password securely
|
3. Can hash password securely
|
||||||
|
|
||||||
### Regression Evals
|
### Regression Evals
|
||||||
|
|
||||||
1. Existing login still works
|
1. Existing login still works
|
||||||
2. Session management unchanged
|
2. Session management unchanged
|
||||||
3. Logout flow intact
|
3. Logout flow intact
|
||||||
|
|
||||||
### Success Metrics
|
### Success Metrics
|
||||||
|
|
||||||
- pass@3 > 90% for capability evals
|
- pass@3 > 90% for capability evals
|
||||||
- pass^3 = 100% for regression evals
|
- pass^3 = 100% for regression evals
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Implement
|
### 2. Implement
|
||||||
|
|
||||||
Write code to pass the defined evals.
|
Write code to pass the defined evals.
|
||||||
|
|
||||||
### 3. Evaluate
|
### 3. Evaluate
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run capability evals
|
# Run capability evals
|
||||||
[Run each capability eval, record PASS/FAIL]
|
[Run each capability eval, record PASS/FAIL]
|
||||||
@@ -124,9 +148,9 @@ npm test -- --testPathPattern="existing"
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 4. Report
|
### 4. Report
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
EVAL REPORT: feature-xyz
|
# EVAL REPORT: feature-xyz
|
||||||
========================
|
|
||||||
|
|
||||||
Capability Evals:
|
Capability Evals:
|
||||||
create-user: PASS (pass@1)
|
create-user: PASS (pass@1)
|
||||||
@@ -150,26 +174,33 @@ Status: READY FOR REVIEW
|
|||||||
## Integration Patterns
|
## Integration Patterns
|
||||||
|
|
||||||
### Pre-Implementation
|
### Pre-Implementation
|
||||||
|
|
||||||
```
|
```
|
||||||
/eval define feature-name
|
/eval define feature-name
|
||||||
```
|
```
|
||||||
|
|
||||||
Creates eval definition file at `.cursor/evals/feature-name.md`
|
Creates eval definition file at `.cursor/evals/feature-name.md`
|
||||||
|
|
||||||
### During Implementation
|
### During Implementation
|
||||||
|
|
||||||
```
|
```
|
||||||
/eval check feature-name
|
/eval check feature-name
|
||||||
```
|
```
|
||||||
|
|
||||||
Runs current evals and reports status
|
Runs current evals and reports status
|
||||||
|
|
||||||
### Post-Implementation
|
### Post-Implementation
|
||||||
|
|
||||||
```
|
```
|
||||||
/eval report feature-name
|
/eval report feature-name
|
||||||
```
|
```
|
||||||
|
|
||||||
Generates full eval report
|
Generates full eval report
|
||||||
|
|
||||||
## Eval Storage
|
## Eval Storage
|
||||||
|
|
||||||
Store evals in project:
|
Store evals in project:
|
||||||
|
|
||||||
```
|
```
|
||||||
.cursor/
|
.cursor/
|
||||||
evals/
|
evals/
|
||||||
@@ -194,7 +225,9 @@ Store evals in project:
|
|||||||
## EVAL: add-authentication
|
## EVAL: add-authentication
|
||||||
|
|
||||||
### Phase 1: Define (10 min)
|
### Phase 1: Define (10 min)
|
||||||
|
|
||||||
Capability Evals:
|
Capability Evals:
|
||||||
|
|
||||||
- [ ] User can register with email/password
|
- [ ] User can register with email/password
|
||||||
- [ ] User can login with valid credentials
|
- [ ] User can login with valid credentials
|
||||||
- [ ] Invalid credentials rejected with proper error
|
- [ ] Invalid credentials rejected with proper error
|
||||||
@@ -202,19 +235,23 @@ Capability Evals:
|
|||||||
- [ ] Logout clears session
|
- [ ] Logout clears session
|
||||||
|
|
||||||
Regression Evals:
|
Regression Evals:
|
||||||
|
|
||||||
- [ ] Public routes still accessible
|
- [ ] Public routes still accessible
|
||||||
- [ ] API responses unchanged
|
- [ ] API responses unchanged
|
||||||
- [ ] Database schema compatible
|
- [ ] Database schema compatible
|
||||||
|
|
||||||
### Phase 2: Implement (varies)
|
### Phase 2: Implement (varies)
|
||||||
|
|
||||||
[Write code]
|
[Write code]
|
||||||
|
|
||||||
### Phase 3: Evaluate
|
### Phase 3: Evaluate
|
||||||
|
|
||||||
Run: /eval check add-authentication
|
Run: /eval check add-authentication
|
||||||
|
|
||||||
### Phase 4: Report
|
### Phase 4: Report
|
||||||
EVAL REPORT: add-authentication
|
|
||||||
==============================
|
# EVAL REPORT: add-authentication
|
||||||
|
|
||||||
Capability: 5/5 passed (pass@3: 100%)
|
Capability: 5/5 passed (pass@3: 100%)
|
||||||
Regression: 3/3 passed (pass^3: 100%)
|
Regression: 3/3 passed (pass^3: 100%)
|
||||||
Status: SHIP IT
|
Status: SHIP IT
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ Auth lives under `src/apps/auth/`. Shared-across-modules code lives in `src/core
|
|||||||
### Module config + factory
|
### Module config + factory
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { ModuleConfigEntity } from '@repo/ui/foundations'
|
import { ModuleConfigEntity } from '@repo/ui/foundations';
|
||||||
|
|
||||||
export const fullPageModuleConfig: ModuleConfigEntity = {
|
export const fullPageModuleConfig: ModuleConfigEntity = {
|
||||||
moduleKey: 'EXAMPLE_FULL_PAGE',
|
moduleKey: 'EXAMPLE_FULL_PAGE',
|
||||||
@@ -91,28 +91,28 @@ export const fullPageModuleConfig: ModuleConfigEntity = {
|
|||||||
webUrl: '/app/example/full-page',
|
webUrl: '/app/example/full-page',
|
||||||
moduleCategory: 'FULL_PAGE',
|
moduleCategory: 'FULL_PAGE',
|
||||||
moduleType: 'MASTER_DATA',
|
moduleType: 'MASTER_DATA',
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { EnterpriseModuleProvider } from '@repo/ui/foundations'
|
import { EnterpriseModuleProvider } from '@repo/ui/foundations';
|
||||||
import { registerModuleNamespace } from '@repo/core-i18n'
|
import { registerModuleNamespace } from '@repo/core-i18n';
|
||||||
import { apiClient } from '../../../../../../../core/lib/api-client'
|
import { apiClient } from '../../../../../../../core/lib/api-client';
|
||||||
|
|
||||||
registerModuleNamespace(fullPageModuleConfig.translationNamespace, { id, en })
|
registerModuleNamespace(fullPageModuleConfig.translationNamespace, { id, en });
|
||||||
```
|
```
|
||||||
|
|
||||||
### Validators (Zod)
|
### Validators (Zod)
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { z } from 'zod'
|
import { z } from 'zod';
|
||||||
import { compose, required, rangeLength } from '@repo/ui/validators'
|
import { compose, required, rangeLength } from '@repo/ui/validators';
|
||||||
|
|
||||||
export const createFullPageSchema = (t: (key: string) => string) =>
|
export const createFullPageSchema = (t: (key: string) => string) =>
|
||||||
z.object({
|
z.object({
|
||||||
code: compose(z.string(), required(t('common:fields.code'))),
|
code: compose(z.string(), required(t('common:fields.code'))),
|
||||||
name: compose(z.string(), required(t('common:fields.name')), rangeLength(3, 50, t('common:fields.name'))),
|
name: compose(z.string(), required(t('common:fields.name')), rangeLength(3, 50, t('common:fields.name'))),
|
||||||
})
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### Forms and HTTP
|
### Forms and HTTP
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ Client-side security for this React/Electron frontend. There is no NestJS API or
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// NEVER
|
// NEVER
|
||||||
const apiKey = "sk-proj-xxxxx"
|
const apiKey = 'sk-proj-xxxxx';
|
||||||
|
|
||||||
// ALWAYS
|
// ALWAYS
|
||||||
import { ENV } from '../environment'
|
import { ENV } from '../environment';
|
||||||
if (!ENV.API_BASE_URL) throw new Error('VITE_API_BASE_URL is not configured')
|
if (!ENV.API_BASE_URL) throw new Error('VITE_API_BASE_URL is not configured');
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] No hardcoded secrets
|
- [ ] No hardcoded secrets
|
||||||
|
|||||||
@@ -10,11 +10,13 @@ Suggests manual `/compact` at strategic points in your workflow rather than rely
|
|||||||
## Why Strategic Compaction?
|
## Why Strategic Compaction?
|
||||||
|
|
||||||
Auto-compaction triggers at arbitrary points:
|
Auto-compaction triggers at arbitrary points:
|
||||||
|
|
||||||
- Often mid-task, losing important context
|
- Often mid-task, losing important context
|
||||||
- No awareness of logical task boundaries
|
- No awareness of logical task boundaries
|
||||||
- Can interrupt complex multi-step operations
|
- Can interrupt complex multi-step operations
|
||||||
|
|
||||||
Strategic compaction at logical boundaries:
|
Strategic compaction at logical boundaries:
|
||||||
|
|
||||||
- **After exploration, before execution** - Compact research context, keep implementation plan
|
- **After exploration, before execution** - Compact research context, keep implementation plan
|
||||||
- **After completing a milestone** - Fresh start for next phase
|
- **After completing a milestone** - Fresh start for next phase
|
||||||
- **Before major context shifts** - Clear exploration context before different task
|
- **Before major context shifts** - Clear exploration context before different task
|
||||||
@@ -47,6 +49,7 @@ Already wired in `.cursor/hooks.json` as an `afterFileEdit` command:
|
|||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Environment variables:
|
Environment variables:
|
||||||
|
|
||||||
- `COMPACT_THRESHOLD` - Tool calls before first suggestion (default: 50)
|
- `COMPACT_THRESHOLD` - Tool calls before first suggestion (default: 50)
|
||||||
|
|
||||||
## Best Practices
|
## Best Practices
|
||||||
@@ -54,7 +57,7 @@ Environment variables:
|
|||||||
1. **Compact after planning** - Once plan is finalized, compact to start fresh
|
1. **Compact after planning** - Once plan is finalized, compact to start fresh
|
||||||
2. **Compact after debugging** - Clear error-resolution context before continuing
|
2. **Compact after debugging** - Clear error-resolution context before continuing
|
||||||
3. **Don't compact mid-implementation** - Preserve context for related changes
|
3. **Don't compact mid-implementation** - Preserve context for related changes
|
||||||
4. **Read the suggestion** - The hook tells you *when*, you decide *if*
|
4. **Read the suggestion** - The hook tells you _when_, you decide _if_
|
||||||
|
|
||||||
## Related
|
## Related
|
||||||
|
|
||||||
|
|||||||
@@ -38,31 +38,31 @@ TDD for this frontend monorepo (Vitest, not NestJS/Supertest).
|
|||||||
## Unit example
|
## Unit example
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { describe, it, expect } from 'vitest'
|
import { describe, it, expect } from 'vitest';
|
||||||
import { createFullPageSchema } from './full-page.validator'
|
import { createFullPageSchema } from './full-page.validator';
|
||||||
|
|
||||||
describe('createFullPageSchema', () => {
|
describe('createFullPageSchema', () => {
|
||||||
const t = (key: string) => key
|
const t = (key: string) => key;
|
||||||
|
|
||||||
it('rejects an empty code', () => {
|
it('rejects an empty code', () => {
|
||||||
const result = createFullPageSchema(t).safeParse({ code: '', name: 'Widget' })
|
const result = createFullPageSchema(t).safeParse({ code: '', name: 'Widget' });
|
||||||
expect(result.success).toBe(false)
|
expect(result.success).toBe(false);
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
## Component example
|
## Component example
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { render, screen } from '@testing-library/react'
|
import { render, screen } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event';
|
||||||
|
|
||||||
it('calls onClick', async () => {
|
it('calls onClick', async () => {
|
||||||
const onClick = vi.fn()
|
const onClick = vi.fn();
|
||||||
render(<Button onClick={onClick}>Save</Button>)
|
render(<Button onClick={onClick}>Save</Button>);
|
||||||
await userEvent.click(screen.getByRole('button', { name: 'Save' }))
|
await userEvent.click(screen.getByRole('button', { name: 'Save' }));
|
||||||
expect(onClick).toHaveBeenCalledTimes(1)
|
expect(onClick).toHaveBeenCalledTimes(1);
|
||||||
})
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
## Mocking
|
## Mocking
|
||||||
@@ -72,7 +72,7 @@ Mock `@repo/core-api` and `apiClient`, not a database.
|
|||||||
```ts
|
```ts
|
||||||
vi.mock('@repo/core-api/http-client', () => ({
|
vi.mock('@repo/core-api/http-client', () => ({
|
||||||
createHttpClient: () => ({ get: vi.fn(), post: vi.fn(), put: vi.fn(), delete: vi.fn() }),
|
createHttpClient: () => ({ get: vi.fn(), post: vi.fn(), put: vi.fn(), delete: vi.fn() }),
|
||||||
}))
|
}));
|
||||||
```
|
```
|
||||||
|
|
||||||
## File organization
|
## File organization
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ A comprehensive verification system for Claude Code sessions.
|
|||||||
## When to Use
|
## When to Use
|
||||||
|
|
||||||
Invoke this skill:
|
Invoke this skill:
|
||||||
|
|
||||||
- After completing a feature or significant code change
|
- After completing a feature or significant code change
|
||||||
- Before creating a PR
|
- Before creating a PR
|
||||||
- When you want to ensure quality gates pass
|
- When you want to ensure quality gates pass
|
||||||
@@ -13,6 +14,7 @@ Invoke this skill:
|
|||||||
## Verification Phases
|
## Verification Phases
|
||||||
|
|
||||||
### Phase 1: Build Verification
|
### Phase 1: Build Verification
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check if project builds
|
# Check if project builds
|
||||||
npm run build 2>&1 | tail -20
|
npm run build 2>&1 | tail -20
|
||||||
@@ -23,6 +25,7 @@ pnpm build 2>&1 | tail -20
|
|||||||
If build fails, STOP and fix before continuing.
|
If build fails, STOP and fix before continuing.
|
||||||
|
|
||||||
### Phase 2: Type Check
|
### Phase 2: Type Check
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# TypeScript projects
|
# TypeScript projects
|
||||||
npx tsc --noEmit 2>&1 | head -30
|
npx tsc --noEmit 2>&1 | head -30
|
||||||
@@ -34,6 +37,7 @@ pyright . 2>&1 | head -30
|
|||||||
Report all type errors. Fix critical ones before continuing.
|
Report all type errors. Fix critical ones before continuing.
|
||||||
|
|
||||||
### Phase 3: Lint Check
|
### Phase 3: Lint Check
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# JavaScript/TypeScript
|
# JavaScript/TypeScript
|
||||||
npm run lint 2>&1 | head -30
|
npm run lint 2>&1 | head -30
|
||||||
@@ -43,6 +47,7 @@ ruff check . 2>&1 | head -30
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Phase 4: Test Suite
|
### Phase 4: Test Suite
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run tests with coverage
|
# Run tests with coverage
|
||||||
npm run test -- --coverage 2>&1 | tail -50
|
npm run test -- --coverage 2>&1 | tail -50
|
||||||
@@ -52,12 +57,14 @@ npm run test -- --coverage 2>&1 | tail -50
|
|||||||
```
|
```
|
||||||
|
|
||||||
Report:
|
Report:
|
||||||
|
|
||||||
- Total tests: X
|
- Total tests: X
|
||||||
- Passed: X
|
- Passed: X
|
||||||
- Failed: X
|
- Failed: X
|
||||||
- Coverage: X%
|
- Coverage: X%
|
||||||
|
|
||||||
### Phase 5: Security Scan
|
### Phase 5: Security Scan
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check for secrets
|
# Check for secrets
|
||||||
grep -rn "sk-" --include="*.ts" --include="*.js" . 2>/dev/null | head -10
|
grep -rn "sk-" --include="*.ts" --include="*.js" . 2>/dev/null | head -10
|
||||||
@@ -68,6 +75,7 @@ grep -rn "console.log" --include="*.ts" --include="*.tsx" src/ 2>/dev/null | hea
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Phase 6: Diff Review
|
### Phase 6: Diff Review
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Show what changed
|
# Show what changed
|
||||||
git diff --stat
|
git diff --stat
|
||||||
@@ -75,6 +83,7 @@ git diff HEAD~1 --name-only
|
|||||||
```
|
```
|
||||||
|
|
||||||
Review each changed file for:
|
Review each changed file for:
|
||||||
|
|
||||||
- Unintended changes
|
- Unintended changes
|
||||||
- Missing error handling
|
- Missing error handling
|
||||||
- Potential edge cases
|
- Potential edge cases
|
||||||
@@ -107,6 +116,7 @@ For long sessions, run verification every 15 minutes or after major changes:
|
|||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
Set a mental checkpoint:
|
Set a mental checkpoint:
|
||||||
|
|
||||||
- After completing each function
|
- After completing each function
|
||||||
- After finishing a component
|
- After finishing a component
|
||||||
- Before moving to next task
|
- Before moving to next task
|
||||||
|
|||||||
@@ -19,18 +19,21 @@ You are a senior software architect specializing in scalable, maintainable syste
|
|||||||
## Architecture Review Process
|
## Architecture Review Process
|
||||||
|
|
||||||
### 1. Current State Analysis
|
### 1. Current State Analysis
|
||||||
|
|
||||||
- Review existing architecture
|
- Review existing architecture
|
||||||
- Identify patterns and conventions
|
- Identify patterns and conventions
|
||||||
- Document technical debt
|
- Document technical debt
|
||||||
- Assess scalability limitations
|
- Assess scalability limitations
|
||||||
|
|
||||||
### 2. Requirements Gathering
|
### 2. Requirements Gathering
|
||||||
|
|
||||||
- Functional requirements
|
- Functional requirements
|
||||||
- Non-functional requirements (performance, security, scalability)
|
- Non-functional requirements (performance, security, scalability)
|
||||||
- Integration points
|
- Integration points
|
||||||
- Data flow requirements
|
- Data flow requirements
|
||||||
|
|
||||||
### 3. Design Proposal
|
### 3. Design Proposal
|
||||||
|
|
||||||
- High-level architecture diagram
|
- High-level architecture diagram
|
||||||
- Component responsibilities
|
- Component responsibilities
|
||||||
- Data models
|
- Data models
|
||||||
@@ -38,7 +41,9 @@ You are a senior software architect specializing in scalable, maintainable syste
|
|||||||
- Integration patterns
|
- Integration patterns
|
||||||
|
|
||||||
### 4. Trade-Off Analysis
|
### 4. Trade-Off Analysis
|
||||||
|
|
||||||
For each design decision, document:
|
For each design decision, document:
|
||||||
|
|
||||||
- **Pros**: Benefits and advantages
|
- **Pros**: Benefits and advantages
|
||||||
- **Cons**: Drawbacks and limitations
|
- **Cons**: Drawbacks and limitations
|
||||||
- **Alternatives**: Other options considered
|
- **Alternatives**: Other options considered
|
||||||
@@ -47,12 +52,14 @@ For each design decision, document:
|
|||||||
## Architectural Principles
|
## Architectural Principles
|
||||||
|
|
||||||
### 1. Modularity & Separation of Concerns
|
### 1. Modularity & Separation of Concerns
|
||||||
|
|
||||||
- Single Responsibility Principle
|
- Single Responsibility Principle
|
||||||
- High cohesion, low coupling
|
- High cohesion, low coupling
|
||||||
- Clear interfaces between components
|
- Clear interfaces between components
|
||||||
- Independent deployability
|
- Independent deployability
|
||||||
|
|
||||||
### 2. Scalability
|
### 2. Scalability
|
||||||
|
|
||||||
- Horizontal scaling capability
|
- Horizontal scaling capability
|
||||||
- Stateless design where possible
|
- Stateless design where possible
|
||||||
- Efficient database queries
|
- Efficient database queries
|
||||||
@@ -60,6 +67,7 @@ For each design decision, document:
|
|||||||
- Load balancing considerations
|
- Load balancing considerations
|
||||||
|
|
||||||
### 3. Maintainability
|
### 3. Maintainability
|
||||||
|
|
||||||
- Clear code organization
|
- Clear code organization
|
||||||
- Consistent patterns
|
- Consistent patterns
|
||||||
- Comprehensive documentation
|
- Comprehensive documentation
|
||||||
@@ -67,6 +75,7 @@ For each design decision, document:
|
|||||||
- Simple to understand
|
- Simple to understand
|
||||||
|
|
||||||
### 4. Security
|
### 4. Security
|
||||||
|
|
||||||
- Defense in depth
|
- Defense in depth
|
||||||
- Principle of least privilege
|
- Principle of least privilege
|
||||||
- Input validation at boundaries
|
- Input validation at boundaries
|
||||||
@@ -74,6 +83,7 @@ For each design decision, document:
|
|||||||
- Audit trail
|
- Audit trail
|
||||||
|
|
||||||
### 5. Performance
|
### 5. Performance
|
||||||
|
|
||||||
- Efficient algorithms
|
- Efficient algorithms
|
||||||
- Minimal network requests
|
- Minimal network requests
|
||||||
- Optimized database queries
|
- Optimized database queries
|
||||||
@@ -83,6 +93,7 @@ For each design decision, document:
|
|||||||
## Common Patterns
|
## Common Patterns
|
||||||
|
|
||||||
### Frontend Patterns
|
### Frontend Patterns
|
||||||
|
|
||||||
- **Component Composition**: Build complex UI from simple components
|
- **Component Composition**: Build complex UI from simple components
|
||||||
- **Container/Presenter**: Separate data logic from presentation
|
- **Container/Presenter**: Separate data logic from presentation
|
||||||
- **Custom Hooks**: Reusable stateful logic
|
- **Custom Hooks**: Reusable stateful logic
|
||||||
@@ -90,6 +101,7 @@ For each design decision, document:
|
|||||||
- **Code Splitting**: Lazy load routes and heavy components
|
- **Code Splitting**: Lazy load routes and heavy components
|
||||||
|
|
||||||
### Backend Patterns
|
### Backend Patterns
|
||||||
|
|
||||||
- **Repository Pattern**: Abstract data access
|
- **Repository Pattern**: Abstract data access
|
||||||
- **Service Layer**: Business logic separation
|
- **Service Layer**: Business logic separation
|
||||||
- **Middleware Pattern**: Request/response processing
|
- **Middleware Pattern**: Request/response processing
|
||||||
@@ -97,6 +109,7 @@ For each design decision, document:
|
|||||||
- **CQRS**: Separate read and write operations
|
- **CQRS**: Separate read and write operations
|
||||||
|
|
||||||
### Data Patterns
|
### Data Patterns
|
||||||
|
|
||||||
- **Normalized Database**: Reduce redundancy
|
- **Normalized Database**: Reduce redundancy
|
||||||
- **Denormalized for Read Performance**: Optimize queries
|
- **Denormalized for Read Performance**: Optimize queries
|
||||||
- **Event Sourcing**: Audit trail and replayability
|
- **Event Sourcing**: Audit trail and replayability
|
||||||
@@ -111,25 +124,31 @@ For significant architectural decisions, create ADRs:
|
|||||||
# ADR-001: Feature modules live in apps/web, shared UI in packages/ui
|
# ADR-001: Feature modules live in apps/web, shared UI in packages/ui
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
|
|
||||||
Need a default place for product screens vs reusable components.
|
Need a default place for product screens vs reusable components.
|
||||||
|
|
||||||
## Decision
|
## Decision
|
||||||
|
|
||||||
Product modules under `apps/web/src/apps/main/modules/` (copy `example/full-page`). Shared primitives in `packages/ui`. Cross-module app code in `src/core/`.
|
Product modules under `apps/web/src/apps/main/modules/` (copy `example/full-page`). Shared primitives in `packages/ui`. Cross-module app code in `src/core/`.
|
||||||
|
|
||||||
## Consequences
|
## Consequences
|
||||||
|
|
||||||
### Positive
|
### Positive
|
||||||
|
|
||||||
- Clear promotion path: module → core → package
|
- Clear promotion path: module → core → package
|
||||||
- Showcase and docs stay free of product logic
|
- Showcase and docs stay free of product logic
|
||||||
|
|
||||||
### Negative
|
### Negative
|
||||||
|
|
||||||
- Easy to over-share too early (YAGNI)
|
- Easy to over-share too early (YAGNI)
|
||||||
|
|
||||||
### Alternatives Considered
|
### Alternatives Considered
|
||||||
|
|
||||||
- All UI in apps/web (duplicates landing/showcase)
|
- All UI in apps/web (duplicates landing/showcase)
|
||||||
- All features in packages/ui (mixes product with design system)
|
- All features in packages/ui (mixes product with design system)
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
Accepted
|
Accepted
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -138,18 +157,21 @@ Accepted
|
|||||||
When designing a new system or feature:
|
When designing a new system or feature:
|
||||||
|
|
||||||
### Functional Requirements
|
### Functional Requirements
|
||||||
|
|
||||||
- [ ] User stories documented
|
- [ ] User stories documented
|
||||||
- [ ] API contracts defined
|
- [ ] API contracts defined
|
||||||
- [ ] Data models specified
|
- [ ] Data models specified
|
||||||
- [ ] UI/UX flows mapped
|
- [ ] UI/UX flows mapped
|
||||||
|
|
||||||
### Non-Functional Requirements
|
### Non-Functional Requirements
|
||||||
|
|
||||||
- [ ] Performance targets defined (latency, throughput)
|
- [ ] Performance targets defined (latency, throughput)
|
||||||
- [ ] Scalability requirements specified
|
- [ ] Scalability requirements specified
|
||||||
- [ ] Security requirements identified
|
- [ ] Security requirements identified
|
||||||
- [ ] Availability targets set (uptime %)
|
- [ ] Availability targets set (uptime %)
|
||||||
|
|
||||||
### Technical Design
|
### Technical Design
|
||||||
|
|
||||||
- [ ] Architecture diagram created
|
- [ ] Architecture diagram created
|
||||||
- [ ] Component responsibilities defined
|
- [ ] Component responsibilities defined
|
||||||
- [ ] Data flow documented
|
- [ ] Data flow documented
|
||||||
@@ -158,6 +180,7 @@ When designing a new system or feature:
|
|||||||
- [ ] Testing strategy planned
|
- [ ] Testing strategy planned
|
||||||
|
|
||||||
### Operations
|
### Operations
|
||||||
|
|
||||||
- [ ] Deployment strategy defined
|
- [ ] Deployment strategy defined
|
||||||
- [ ] Monitoring and alerting planned
|
- [ ] Monitoring and alerting planned
|
||||||
- [ ] Backup and recovery strategy
|
- [ ] Backup and recovery strategy
|
||||||
@@ -166,6 +189,7 @@ When designing a new system or feature:
|
|||||||
## Red Flags
|
## Red Flags
|
||||||
|
|
||||||
Watch for these architectural anti-patterns:
|
Watch for these architectural anti-patterns:
|
||||||
|
|
||||||
- **Big Ball of Mud**: No clear structure
|
- **Big Ball of Mud**: No clear structure
|
||||||
- **Golden Hammer**: Using same solution for everything
|
- **Golden Hammer**: Using same solution for everything
|
||||||
- **Premature Optimization**: Optimizing too early
|
- **Premature Optimization**: Optimizing too early
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ model: opus
|
|||||||
You are a senior code reviewer ensuring high standards of code quality and security.
|
You are a senior code reviewer ensuring high standards of code quality and security.
|
||||||
|
|
||||||
When invoked:
|
When invoked:
|
||||||
|
|
||||||
1. Run git diff to see recent changes
|
1. Run git diff to see recent changes
|
||||||
2. Focus on modified files
|
2. Focus on modified files
|
||||||
3. Begin review immediately
|
3. Begin review immediately
|
||||||
|
|
||||||
Review checklist:
|
Review checklist:
|
||||||
|
|
||||||
- Code is simple and readable
|
- Code is simple and readable
|
||||||
- Functions and variables are well-named
|
- Functions and variables are well-named
|
||||||
- No duplicated code
|
- No duplicated code
|
||||||
@@ -25,6 +27,7 @@ Review checklist:
|
|||||||
- Licenses of integrated libraries checked
|
- Licenses of integrated libraries checked
|
||||||
|
|
||||||
Provide feedback organized by priority:
|
Provide feedback organized by priority:
|
||||||
|
|
||||||
- Critical issues (must fix)
|
- Critical issues (must fix)
|
||||||
- Warnings (should fix)
|
- Warnings (should fix)
|
||||||
- Suggestions (consider improving)
|
- Suggestions (consider improving)
|
||||||
@@ -74,6 +77,7 @@ Include specific examples of how to fix issues.
|
|||||||
## Review Output Format
|
## Review Output Format
|
||||||
|
|
||||||
For each issue:
|
For each issue:
|
||||||
|
|
||||||
```
|
```
|
||||||
[CRITICAL] Hardcoded API key
|
[CRITICAL] Hardcoded API key
|
||||||
File: src/core/lib/api-client.ts:42
|
File: src/core/lib/api-client.ts:42
|
||||||
|
|||||||
@@ -42,14 +42,14 @@ Canonical sample: `apps/web/src/apps/main/modules/example/full-page/`. Copy that
|
|||||||
### Package component tests (Testing Library)
|
### Package component tests (Testing Library)
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { render, screen } from '@testing-library/react'
|
import { render, screen } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event';
|
||||||
import { FieldTextInput } from '@repo/ui/form'
|
import { FieldTextInput } from '@repo/ui/form';
|
||||||
|
|
||||||
it('renders the field label', () => {
|
it('renders the field label', () => {
|
||||||
render(<FieldTextInput name="code" label="Code" />)
|
render(<FieldTextInput name="code" label="Code" />);
|
||||||
expect(screen.getByLabelText('Code')).toBeInTheDocument()
|
expect(screen.getByLabelText('Code')).toBeInTheDocument();
|
||||||
})
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### Mock remote data services (not a database)
|
### Mock remote data services (not a database)
|
||||||
@@ -63,7 +63,7 @@ vi.mock('../../domain/factories', () => ({
|
|||||||
update: vi.fn(),
|
update: vi.fn(),
|
||||||
delete: vi.fn(),
|
delete: vi.fn(),
|
||||||
},
|
},
|
||||||
}))
|
}));
|
||||||
```
|
```
|
||||||
|
|
||||||
## Browser verification (`apps/web`)
|
## Browser verification (`apps/web`)
|
||||||
@@ -92,9 +92,11 @@ Do not add Playwright unless the user explicitly asks.
|
|||||||
**Command:** pnpm test
|
**Command:** pnpm test
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
- Total / passed / failed
|
- Total / passed / failed
|
||||||
|
|
||||||
## Failed
|
## Failed
|
||||||
|
|
||||||
- File — assertion
|
- File — assertion
|
||||||
- Recommended fix
|
- Recommended fix
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -18,19 +18,23 @@ You are an expert planning specialist focused on creating comprehensive, actiona
|
|||||||
## Planning Process
|
## Planning Process
|
||||||
|
|
||||||
### 1. Requirements Analysis
|
### 1. Requirements Analysis
|
||||||
|
|
||||||
- Understand the feature request completely
|
- Understand the feature request completely
|
||||||
- Ask clarifying questions if needed
|
- Ask clarifying questions if needed
|
||||||
- Identify success criteria
|
- Identify success criteria
|
||||||
- List assumptions and constraints
|
- List assumptions and constraints
|
||||||
|
|
||||||
### 2. Architecture Review
|
### 2. Architecture Review
|
||||||
|
|
||||||
- Analyze existing codebase structure
|
- Analyze existing codebase structure
|
||||||
- Identify affected components
|
- Identify affected components
|
||||||
- Review similar implementations
|
- Review similar implementations
|
||||||
- Consider reusable patterns
|
- Consider reusable patterns
|
||||||
|
|
||||||
### 3. Step Breakdown
|
### 3. Step Breakdown
|
||||||
|
|
||||||
Create detailed steps with:
|
Create detailed steps with:
|
||||||
|
|
||||||
- Clear, specific actions
|
- Clear, specific actions
|
||||||
- File paths and locations
|
- File paths and locations
|
||||||
- Dependencies between steps
|
- Dependencies between steps
|
||||||
@@ -38,6 +42,7 @@ Create detailed steps with:
|
|||||||
- Potential risks
|
- Potential risks
|
||||||
|
|
||||||
### 4. Implementation Order
|
### 4. Implementation Order
|
||||||
|
|
||||||
- Prioritize by dependencies
|
- Prioritize by dependencies
|
||||||
- Group related changes
|
- Group related changes
|
||||||
- Minimize context switching
|
- Minimize context switching
|
||||||
@@ -49,20 +54,25 @@ Create detailed steps with:
|
|||||||
# Implementation Plan: [Feature Name]
|
# Implementation Plan: [Feature Name]
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
[2-3 sentence summary]
|
[2-3 sentence summary]
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- [Requirement 1]
|
- [Requirement 1]
|
||||||
- [Requirement 2]
|
- [Requirement 2]
|
||||||
|
|
||||||
## Architecture Changes
|
## Architecture Changes
|
||||||
|
|
||||||
- [Change 1: file path and description]
|
- [Change 1: file path and description]
|
||||||
- [Change 2: file path and description]
|
- [Change 2: file path and description]
|
||||||
|
|
||||||
## Implementation Steps
|
## Implementation Steps
|
||||||
|
|
||||||
### Phase 1: [Phase Name]
|
### Phase 1: [Phase Name]
|
||||||
|
|
||||||
1. **[Step Name]** (File: path/to/file.ts)
|
1. **[Step Name]** (File: path/to/file.ts)
|
||||||
|
|
||||||
- Action: Specific action to take
|
- Action: Specific action to take
|
||||||
- Why: Reason for this step
|
- Why: Reason for this step
|
||||||
- Dependencies: None / Requires step X
|
- Dependencies: None / Requires step X
|
||||||
@@ -72,18 +82,22 @@ Create detailed steps with:
|
|||||||
...
|
...
|
||||||
|
|
||||||
### Phase 2: [Phase Name]
|
### Phase 2: [Phase Name]
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|
||||||
## Testing Strategy
|
## Testing Strategy
|
||||||
|
|
||||||
- Unit tests: [files to test]
|
- Unit tests: [files to test]
|
||||||
- Integration tests: [flows to test]
|
- Integration tests: [flows to test]
|
||||||
- E2E tests: [user journeys to test]
|
- E2E tests: [user journeys to test]
|
||||||
|
|
||||||
## Risks & Mitigations
|
## Risks & Mitigations
|
||||||
|
|
||||||
- **Risk**: [Description]
|
- **Risk**: [Description]
|
||||||
- Mitigation: [How to address]
|
- Mitigation: [How to address]
|
||||||
|
|
||||||
## Success Criteria
|
## Success Criteria
|
||||||
|
|
||||||
- [ ] Criterion 1
|
- [ ] Criterion 1
|
||||||
- [ ] Criterion 2
|
- [ ] Criterion 2
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -20,12 +20,14 @@ You are an expert refactoring specialist focused on code cleanup and consolidati
|
|||||||
## Tools at Your Disposal
|
## Tools at Your Disposal
|
||||||
|
|
||||||
### Detection Tools
|
### Detection Tools
|
||||||
|
|
||||||
- **knip** - Find unused files, exports, dependencies, types
|
- **knip** - Find unused files, exports, dependencies, types
|
||||||
- **depcheck** - Identify unused npm dependencies
|
- **depcheck** - Identify unused npm dependencies
|
||||||
- **ts-prune** - Find unused TypeScript exports
|
- **ts-prune** - Find unused TypeScript exports
|
||||||
- **eslint** - Check for unused disable-directives and variables
|
- **eslint** - Check for unused disable-directives and variables
|
||||||
|
|
||||||
### Analysis Commands
|
### Analysis Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run knip for unused exports/files/dependencies
|
# Run knip for unused exports/files/dependencies
|
||||||
npx knip
|
npx knip
|
||||||
@@ -43,6 +45,7 @@ npx eslint . --report-unused-disable-directives
|
|||||||
## Refactoring Workflow
|
## Refactoring Workflow
|
||||||
|
|
||||||
### 1. Analysis Phase
|
### 1. Analysis Phase
|
||||||
|
|
||||||
```
|
```
|
||||||
a) Run detection tools in parallel
|
a) Run detection tools in parallel
|
||||||
b) Collect all findings
|
b) Collect all findings
|
||||||
@@ -53,6 +56,7 @@ c) Categorize by risk level:
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 2. Risk Assessment
|
### 2. Risk Assessment
|
||||||
|
|
||||||
```
|
```
|
||||||
For each item to remove:
|
For each item to remove:
|
||||||
- Check if it's imported anywhere (grep search)
|
- Check if it's imported anywhere (grep search)
|
||||||
@@ -63,6 +67,7 @@ For each item to remove:
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 3. Safe Removal Process
|
### 3. Safe Removal Process
|
||||||
|
|
||||||
```
|
```
|
||||||
a) Start with SAFE items only
|
a) Start with SAFE items only
|
||||||
b) Remove one category at a time:
|
b) Remove one category at a time:
|
||||||
@@ -75,6 +80,7 @@ d) Create git commit for each batch
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 4. Duplicate Consolidation
|
### 4. Duplicate Consolidation
|
||||||
|
|
||||||
```
|
```
|
||||||
a) Find duplicate components/utilities
|
a) Find duplicate components/utilities
|
||||||
b) Choose the best implementation:
|
b) Choose the best implementation:
|
||||||
@@ -96,28 +102,34 @@ Create/update `docs/DELETION_LOG.md` with this structure:
|
|||||||
## [YYYY-MM-DD] Refactor Session
|
## [YYYY-MM-DD] Refactor Session
|
||||||
|
|
||||||
### Unused Dependencies Removed
|
### Unused Dependencies Removed
|
||||||
|
|
||||||
- package-name@version - Last used: never, Size: XX KB
|
- package-name@version - Last used: never, Size: XX KB
|
||||||
- another-package@version - Replaced by: better-package
|
- another-package@version - Replaced by: better-package
|
||||||
|
|
||||||
### Unused Files Deleted
|
### Unused Files Deleted
|
||||||
|
|
||||||
- src/old-component.tsx - Replaced by: src/new-component.tsx
|
- src/old-component.tsx - Replaced by: src/new-component.tsx
|
||||||
- lib/deprecated-util.ts - Functionality moved to: lib/utils.ts
|
- lib/deprecated-util.ts - Functionality moved to: lib/utils.ts
|
||||||
|
|
||||||
### Duplicate Code Consolidated
|
### Duplicate Code Consolidated
|
||||||
|
|
||||||
- src/components/Button1.tsx + Button2.tsx → Button.tsx
|
- src/components/Button1.tsx + Button2.tsx → Button.tsx
|
||||||
- Reason: Both implementations were identical
|
- Reason: Both implementations were identical
|
||||||
|
|
||||||
### Unused Exports Removed
|
### Unused Exports Removed
|
||||||
|
|
||||||
- src/utils/helpers.ts - Functions: foo(), bar()
|
- src/utils/helpers.ts - Functions: foo(), bar()
|
||||||
- Reason: No references found in codebase
|
- Reason: No references found in codebase
|
||||||
|
|
||||||
### Impact
|
### Impact
|
||||||
|
|
||||||
- Files deleted: 15
|
- Files deleted: 15
|
||||||
- Dependencies removed: 5
|
- Dependencies removed: 5
|
||||||
- Lines of code removed: 2,300
|
- Lines of code removed: 2,300
|
||||||
- Bundle size reduction: ~45 KB
|
- Bundle size reduction: ~45 KB
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
- All unit tests passing: ✓
|
- All unit tests passing: ✓
|
||||||
- All integration tests passing: ✓
|
- All integration tests passing: ✓
|
||||||
- Manual testing completed: ✓
|
- Manual testing completed: ✓
|
||||||
@@ -126,6 +138,7 @@ Create/update `docs/DELETION_LOG.md` with this structure:
|
|||||||
## Safety Checklist
|
## Safety Checklist
|
||||||
|
|
||||||
Before removing ANYTHING:
|
Before removing ANYTHING:
|
||||||
|
|
||||||
- [ ] Run detection tools
|
- [ ] Run detection tools
|
||||||
- [ ] Grep for all references
|
- [ ] Grep for all references
|
||||||
- [ ] Check dynamic imports
|
- [ ] Check dynamic imports
|
||||||
@@ -136,6 +149,7 @@ Before removing ANYTHING:
|
|||||||
- [ ] Document in DELETION_LOG.md
|
- [ ] Document in DELETION_LOG.md
|
||||||
|
|
||||||
After each removal:
|
After each removal:
|
||||||
|
|
||||||
- [ ] Build succeeds
|
- [ ] Build succeeds
|
||||||
- [ ] Tests pass
|
- [ ] Tests pass
|
||||||
- [ ] No console errors
|
- [ ] No console errors
|
||||||
@@ -145,20 +159,22 @@ After each removal:
|
|||||||
## Common Patterns to Remove
|
## Common Patterns to Remove
|
||||||
|
|
||||||
### 1. Unused Imports
|
### 1. Unused Imports
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ Remove unused imports
|
// ❌ Remove unused imports
|
||||||
import { useState, useEffect, useMemo } from 'react' // Only useState used
|
import { useState, useEffect, useMemo } from 'react'; // Only useState used
|
||||||
|
|
||||||
// ✅ Keep only what's used
|
// ✅ Keep only what's used
|
||||||
import { useState } from 'react'
|
import { useState } from 'react';
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Dead Code Branches
|
### 2. Dead Code Branches
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ Remove unreachable code
|
// ❌ Remove unreachable code
|
||||||
if (false) {
|
if (false) {
|
||||||
// This never executes
|
// This never executes
|
||||||
doSomething()
|
doSomething();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ❌ Remove unused functions
|
// ❌ Remove unused functions
|
||||||
@@ -168,6 +184,7 @@ export function unusedHelper() {
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 3. Duplicate Components
|
### 3. Duplicate Components
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ Multiple similar components
|
// ❌ Multiple similar components
|
||||||
components/Button.tsx
|
components/Button.tsx
|
||||||
@@ -179,6 +196,7 @@ components/Button.tsx (with variant prop)
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 4. Unused Dependencies
|
### 4. Unused Dependencies
|
||||||
|
|
||||||
```json
|
```json
|
||||||
// ❌ Package installed but not imported
|
// ❌ Package installed but not imported
|
||||||
{
|
{
|
||||||
@@ -192,6 +210,7 @@ components/Button.tsx (with variant prop)
|
|||||||
## Example Project-Specific Rules
|
## Example Project-Specific Rules
|
||||||
|
|
||||||
**CRITICAL - NEVER REMOVE:**
|
**CRITICAL - NEVER REMOVE:**
|
||||||
|
|
||||||
- `apiClient` / `createHttpClient` wiring
|
- `apiClient` / `createHttpClient` wiring
|
||||||
- `terminateAuthSession` / auth interceptors
|
- `terminateAuthSession` / auth interceptors
|
||||||
- `EnterpriseModuleProvider` and FULL_PAGE page providers
|
- `EnterpriseModuleProvider` and FULL_PAGE page providers
|
||||||
@@ -199,6 +218,7 @@ components/Button.tsx (with variant prop)
|
|||||||
- Electron preload / IPC bridge
|
- Electron preload / IPC bridge
|
||||||
|
|
||||||
**SAFE TO REMOVE:**
|
**SAFE TO REMOVE:**
|
||||||
|
|
||||||
- Old unused components in components/ folder
|
- Old unused components in components/ folder
|
||||||
- Deprecated utility functions
|
- Deprecated utility functions
|
||||||
- Test files for deleted features
|
- Test files for deleted features
|
||||||
@@ -206,6 +226,7 @@ components/Button.tsx (with variant prop)
|
|||||||
- Unused TypeScript types/interfaces
|
- Unused TypeScript types/interfaces
|
||||||
|
|
||||||
**ALWAYS VERIFY:**
|
**ALWAYS VERIFY:**
|
||||||
|
|
||||||
- Auth login + `terminateAuthSession`
|
- Auth login + `terminateAuthSession`
|
||||||
- `example/full-page` still routes and loads
|
- `example/full-page` still routes and loads
|
||||||
- `@repo/ui` exports used by web/showcase
|
- `@repo/ui` exports used by web/showcase
|
||||||
@@ -219,26 +240,31 @@ When opening PR with deletions:
|
|||||||
## Refactor: Code Cleanup
|
## Refactor: Code Cleanup
|
||||||
|
|
||||||
### Summary
|
### Summary
|
||||||
|
|
||||||
Dead code cleanup removing unused exports, dependencies, and duplicates.
|
Dead code cleanup removing unused exports, dependencies, and duplicates.
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
- Removed X unused files
|
- Removed X unused files
|
||||||
- Removed Y unused dependencies
|
- Removed Y unused dependencies
|
||||||
- Consolidated Z duplicate components
|
- Consolidated Z duplicate components
|
||||||
- See docs/DELETION_LOG.md for details
|
- See docs/DELETION_LOG.md for details
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
- [x] Build passes
|
- [x] Build passes
|
||||||
- [x] All tests pass
|
- [x] All tests pass
|
||||||
- [x] Manual testing completed
|
- [x] Manual testing completed
|
||||||
- [x] No console errors
|
- [x] No console errors
|
||||||
|
|
||||||
### Impact
|
### Impact
|
||||||
|
|
||||||
- Bundle size: -XX KB
|
- Bundle size: -XX KB
|
||||||
- Lines of code: -XXXX
|
- Lines of code: -XXXX
|
||||||
- Dependencies: -X packages
|
- Dependencies: -X packages
|
||||||
|
|
||||||
### Risk Level
|
### Risk Level
|
||||||
|
|
||||||
🟢 LOW - Only removed verifiably unused code
|
🟢 LOW - Only removed verifiably unused code
|
||||||
|
|
||||||
See DELETION_LOG.md for complete details.
|
See DELETION_LOG.md for complete details.
|
||||||
@@ -249,6 +275,7 @@ See DELETION_LOG.md for complete details.
|
|||||||
If something breaks after removal:
|
If something breaks after removal:
|
||||||
|
|
||||||
1. **Immediate rollback:**
|
1. **Immediate rollback:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git revert HEAD
|
git revert HEAD
|
||||||
pnpm install
|
pnpm install
|
||||||
@@ -257,11 +284,13 @@ If something breaks after removal:
|
|||||||
```
|
```
|
||||||
|
|
||||||
2. **Investigate:**
|
2. **Investigate:**
|
||||||
|
|
||||||
- What failed?
|
- What failed?
|
||||||
- Was it a dynamic import?
|
- Was it a dynamic import?
|
||||||
- Was it used in a way detection tools missed?
|
- Was it used in a way detection tools missed?
|
||||||
|
|
||||||
3. **Fix forward:**
|
3. **Fix forward:**
|
||||||
|
|
||||||
- Mark item as "DO NOT REMOVE" in notes
|
- Mark item as "DO NOT REMOVE" in notes
|
||||||
- Document why detection tools missed it
|
- Document why detection tools missed it
|
||||||
- Add explicit type annotations if needed
|
- Add explicit type annotations if needed
|
||||||
@@ -293,6 +322,7 @@ If something breaks after removal:
|
|||||||
## Success Metrics
|
## Success Metrics
|
||||||
|
|
||||||
After cleanup session:
|
After cleanup session:
|
||||||
|
|
||||||
- ✅ All tests passing
|
- ✅ All tests passing
|
||||||
- ✅ Build succeeds
|
- ✅ Build succeeds
|
||||||
- ✅ No console errors
|
- ✅ No console errors
|
||||||
|
|||||||
@@ -65,7 +65,10 @@ If CRITICAL: stop, fix, rotate any leaked secret, scan for the same pattern.
|
|||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
# Security Review
|
# Security Review
|
||||||
|
|
||||||
**Status:** CLEAR / ISSUES FOUND
|
**Status:** CLEAR / ISSUES FOUND
|
||||||
|
|
||||||
## Critical / High / Medium
|
## Critical / High / Medium
|
||||||
|
|
||||||
- File:line — issue — fix
|
- File:line — issue — fix
|
||||||
```
|
```
|
||||||
|
|||||||
+10
-10
@@ -19,18 +19,18 @@ You are a Test-Driven Development (TDD) specialist. This repo uses **Vitest** (a
|
|||||||
### Step 1: Write the test first (RED)
|
### Step 1: Write the test first (RED)
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { describe, it, expect } from 'vitest'
|
import { describe, it, expect } from 'vitest';
|
||||||
import { createFullPageSchema } from './full-page.validator'
|
import { createFullPageSchema } from './full-page.validator';
|
||||||
|
|
||||||
describe('createFullPageSchema', () => {
|
describe('createFullPageSchema', () => {
|
||||||
const t = (key: string) => key
|
const t = (key: string) => key;
|
||||||
|
|
||||||
it('rejects an empty code', () => {
|
it('rejects an empty code', () => {
|
||||||
const schema = createFullPageSchema(t)
|
const schema = createFullPageSchema(t);
|
||||||
const result = schema.safeParse({ code: '', name: 'Widget' })
|
const result = schema.safeParse({ code: '', name: 'Widget' });
|
||||||
expect(result.success).toBe(false)
|
expect(result.success).toBe(false);
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 2: Run it (must FAIL)
|
### Step 2: Run it (must FAIL)
|
||||||
@@ -47,7 +47,7 @@ export const createFullPageSchema = (t: (key: string) => string) =>
|
|||||||
z.object({
|
z.object({
|
||||||
code: compose(z.string(), required(t('common:fields.code'))),
|
code: compose(z.string(), required(t('common:fields.code'))),
|
||||||
name: compose(z.string(), required(t('common:fields.name')), rangeLength(3, 50, t('common:fields.name'))),
|
name: compose(z.string(), required(t('common:fields.name')), rangeLength(3, 50, t('common:fields.name'))),
|
||||||
})
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 4: Run until green, then refactor. Coverage via `pnpm test` / `pnpm check:all`.
|
### Step 4: Run until green, then refactor. Coverage via `pnpm test` / `pnpm check:all`.
|
||||||
@@ -65,7 +65,7 @@ Mock `@repo/core-api` and `apiClient` — not a database.
|
|||||||
```ts
|
```ts
|
||||||
vi.mock('@repo/core-api/http-client', () => ({
|
vi.mock('@repo/core-api/http-client', () => ({
|
||||||
createHttpClient: () => ({ get: vi.fn(), post: vi.fn(), put: vi.fn(), delete: vi.fn() }),
|
createHttpClient: () => ({ get: vi.fn(), post: vi.fn(), put: vi.fn(), delete: vi.fn() }),
|
||||||
}))
|
}));
|
||||||
```
|
```
|
||||||
|
|
||||||
## Edge cases you MUST test
|
## Edge cases you MUST test
|
||||||
|
|||||||
@@ -5,16 +5,19 @@ Incrementally fix TypeScript and build errors:
|
|||||||
1. Run `pnpm typecheck` or `pnpm build` (this repo uses pnpm + Turbo + Vite)
|
1. Run `pnpm typecheck` or `pnpm build` (this repo uses pnpm + Turbo + Vite)
|
||||||
|
|
||||||
2. Parse error output:
|
2. Parse error output:
|
||||||
|
|
||||||
- Group by file
|
- Group by file
|
||||||
- Sort by severity
|
- Sort by severity
|
||||||
|
|
||||||
3. For each error:
|
3. For each error:
|
||||||
|
|
||||||
- Show 5 lines of context
|
- Show 5 lines of context
|
||||||
- Explain the issue
|
- Explain the issue
|
||||||
- Apply a minimal fix
|
- Apply a minimal fix
|
||||||
- Re-run the failing command
|
- Re-run the failing command
|
||||||
|
|
||||||
4. Stop if:
|
4. Stop if:
|
||||||
|
|
||||||
- The fix introduces new errors
|
- The fix introduces new errors
|
||||||
- The same error persists after 3 attempts
|
- The same error persists after 3 attempts
|
||||||
- The user asks to pause
|
- The user asks to pause
|
||||||
|
|||||||
@@ -26,12 +26,14 @@ When verifying against a checkpoint:
|
|||||||
|
|
||||||
1. Read checkpoint from log
|
1. Read checkpoint from log
|
||||||
2. Compare current state to checkpoint:
|
2. Compare current state to checkpoint:
|
||||||
|
|
||||||
- Files added since checkpoint
|
- Files added since checkpoint
|
||||||
- Files modified since checkpoint
|
- Files modified since checkpoint
|
||||||
- Test pass rate now vs then
|
- Test pass rate now vs then
|
||||||
- Coverage now vs then
|
- Coverage now vs then
|
||||||
|
|
||||||
3. Report:
|
3. Report:
|
||||||
|
|
||||||
```
|
```
|
||||||
CHECKPOINT COMPARISON: $NAME
|
CHECKPOINT COMPARISON: $NAME
|
||||||
============================
|
============================
|
||||||
@@ -44,6 +46,7 @@ Build: [PASS/FAIL]
|
|||||||
## List Checkpoints
|
## List Checkpoints
|
||||||
|
|
||||||
Show all checkpoints with:
|
Show all checkpoints with:
|
||||||
|
|
||||||
- Name
|
- Name
|
||||||
- Timestamp
|
- Timestamp
|
||||||
- Git SHA
|
- Git SHA
|
||||||
@@ -68,6 +71,7 @@ Typical checkpoint flow:
|
|||||||
## Arguments
|
## Arguments
|
||||||
|
|
||||||
$ARGUMENTS:
|
$ARGUMENTS:
|
||||||
|
|
||||||
- `create <name>` - Create named checkpoint
|
- `create <name>` - Create named checkpoint
|
||||||
- `verify <name>` - Verify against named checkpoint
|
- `verify <name>` - Verify against named checkpoint
|
||||||
- `list` - Show all checkpoints
|
- `list` - Show all checkpoints
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ Comprehensive security and quality review of uncommitted changes:
|
|||||||
2. For each changed file, check for:
|
2. For each changed file, check for:
|
||||||
|
|
||||||
**Security Issues (CRITICAL):**
|
**Security Issues (CRITICAL):**
|
||||||
|
|
||||||
- Hardcoded credentials, API keys, tokens
|
- Hardcoded credentials, API keys, tokens
|
||||||
- SQL injection vulnerabilities
|
- SQL injection vulnerabilities
|
||||||
- XSS vulnerabilities
|
- XSS vulnerabilities
|
||||||
@@ -15,6 +16,7 @@ Comprehensive security and quality review of uncommitted changes:
|
|||||||
- Path traversal risks
|
- Path traversal risks
|
||||||
|
|
||||||
**Code Quality (HIGH):**
|
**Code Quality (HIGH):**
|
||||||
|
|
||||||
- Functions > 50 lines
|
- Functions > 50 lines
|
||||||
- Files > 800 lines
|
- Files > 800 lines
|
||||||
- Nesting depth > 4 levels
|
- Nesting depth > 4 levels
|
||||||
@@ -24,12 +26,14 @@ Comprehensive security and quality review of uncommitted changes:
|
|||||||
- Missing JSDoc for public APIs
|
- Missing JSDoc for public APIs
|
||||||
|
|
||||||
**Best Practices (MEDIUM):**
|
**Best Practices (MEDIUM):**
|
||||||
|
|
||||||
- Mutation patterns (use immutable instead)
|
- Mutation patterns (use immutable instead)
|
||||||
- Emoji usage in code/comments
|
- Emoji usage in code/comments
|
||||||
- Missing tests for new code
|
- Missing tests for new code
|
||||||
- Accessibility issues (a11y)
|
- Accessibility issues (a11y)
|
||||||
|
|
||||||
3. Generate report with:
|
3. Generate report with:
|
||||||
|
|
||||||
- Severity: CRITICAL, HIGH, MEDIUM, LOW
|
- Severity: CRITICAL, HIGH, MEDIUM, LOW
|
||||||
- File location and line numbers
|
- File location and line numbers
|
||||||
- Issue description
|
- Issue description
|
||||||
|
|||||||
@@ -16,17 +16,21 @@ Create a new eval definition:
|
|||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
## EVAL: feature-name
|
## EVAL: feature-name
|
||||||
|
|
||||||
Created: $(date)
|
Created: $(date)
|
||||||
|
|
||||||
### Capability Evals
|
### Capability Evals
|
||||||
|
|
||||||
- [ ] [Description of capability 1]
|
- [ ] [Description of capability 1]
|
||||||
- [ ] [Description of capability 2]
|
- [ ] [Description of capability 2]
|
||||||
|
|
||||||
### Regression Evals
|
### Regression Evals
|
||||||
|
|
||||||
- [ ] [Existing behavior 1 still works]
|
- [ ] [Existing behavior 1 still works]
|
||||||
- [ ] [Existing behavior 2 still works]
|
- [ ] [Existing behavior 2 still works]
|
||||||
|
|
||||||
### Success Criteria
|
### Success Criteria
|
||||||
|
|
||||||
- pass@3 > 90% for capability evals
|
- pass@3 > 90% for capability evals
|
||||||
- pass^3 = 100% for regression evals
|
- pass^3 = 100% for regression evals
|
||||||
```
|
```
|
||||||
@@ -113,6 +117,7 @@ feature-export [0/4 passing] NOT STARTED
|
|||||||
## Arguments
|
## Arguments
|
||||||
|
|
||||||
$ARGUMENTS:
|
$ARGUMENTS:
|
||||||
|
|
||||||
- `define <name>` - Create new eval definition
|
- `define <name>` - Create new eval definition
|
||||||
- `check <name>` - Run and check evals
|
- `check <name>` - Run and check evals
|
||||||
- `report <name>` - Generate full report
|
- `report <name>` - Generate full report
|
||||||
|
|||||||
@@ -11,17 +11,20 @@ Run `/learn` at any point during a session when you've solved a non-trivial prob
|
|||||||
Look for:
|
Look for:
|
||||||
|
|
||||||
1. **Error Resolution Patterns**
|
1. **Error Resolution Patterns**
|
||||||
|
|
||||||
- What error occurred?
|
- What error occurred?
|
||||||
- What was the root cause?
|
- What was the root cause?
|
||||||
- What fixed it?
|
- What fixed it?
|
||||||
- Is this reusable for similar errors?
|
- Is this reusable for similar errors?
|
||||||
|
|
||||||
2. **Debugging Techniques**
|
2. **Debugging Techniques**
|
||||||
|
|
||||||
- Non-obvious debugging steps
|
- Non-obvious debugging steps
|
||||||
- Tool combinations that worked
|
- Tool combinations that worked
|
||||||
- Diagnostic patterns
|
- Diagnostic patterns
|
||||||
|
|
||||||
3. **Workarounds**
|
3. **Workarounds**
|
||||||
|
|
||||||
- Library quirks
|
- Library quirks
|
||||||
- API limitations
|
- API limitations
|
||||||
- Version-specific fixes
|
- Version-specific fixes
|
||||||
@@ -42,15 +45,19 @@ Create a skill file at `.agents/skills/learned/[pattern-name].md`:
|
|||||||
**Context:** [Brief description of when this applies]
|
**Context:** [Brief description of when this applies]
|
||||||
|
|
||||||
## Problem
|
## Problem
|
||||||
|
|
||||||
[What problem this solves - be specific]
|
[What problem this solves - be specific]
|
||||||
|
|
||||||
## Solution
|
## Solution
|
||||||
|
|
||||||
[The pattern/technique/workaround]
|
[The pattern/technique/workaround]
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
[Code example if applicable]
|
[Code example if applicable]
|
||||||
|
|
||||||
## When to Use
|
## When to Use
|
||||||
|
|
||||||
[Trigger conditions - what should activate this skill]
|
[Trigger conditions - what should activate this skill]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -9,25 +9,33 @@ Sequential agent workflow for complex tasks.
|
|||||||
## Workflow Types
|
## Workflow Types
|
||||||
|
|
||||||
### feature
|
### feature
|
||||||
|
|
||||||
Full feature implementation workflow:
|
Full feature implementation workflow:
|
||||||
|
|
||||||
```
|
```
|
||||||
planner -> tdd-guide -> code-reviewer -> security-reviewer
|
planner -> tdd-guide -> code-reviewer -> security-reviewer
|
||||||
```
|
```
|
||||||
|
|
||||||
### bugfix
|
### bugfix
|
||||||
|
|
||||||
Bug investigation and fix workflow:
|
Bug investigation and fix workflow:
|
||||||
|
|
||||||
```
|
```
|
||||||
explorer -> tdd-guide -> code-reviewer
|
explorer -> tdd-guide -> code-reviewer
|
||||||
```
|
```
|
||||||
|
|
||||||
### refactor
|
### refactor
|
||||||
|
|
||||||
Safe refactoring workflow:
|
Safe refactoring workflow:
|
||||||
|
|
||||||
```
|
```
|
||||||
architect -> code-reviewer -> tdd-guide
|
architect -> code-reviewer -> tdd-guide
|
||||||
```
|
```
|
||||||
|
|
||||||
### security
|
### security
|
||||||
|
|
||||||
Security-focused review:
|
Security-focused review:
|
||||||
|
|
||||||
```
|
```
|
||||||
security-reviewer -> code-reviewer -> architect
|
security-reviewer -> code-reviewer -> architect
|
||||||
```
|
```
|
||||||
@@ -49,18 +57,23 @@ Between agents, create handoff document:
|
|||||||
## HANDOFF: [previous-agent] -> [next-agent]
|
## HANDOFF: [previous-agent] -> [next-agent]
|
||||||
|
|
||||||
### Context
|
### Context
|
||||||
|
|
||||||
[Summary of what was done]
|
[Summary of what was done]
|
||||||
|
|
||||||
### Findings
|
### Findings
|
||||||
|
|
||||||
[Key discoveries or decisions]
|
[Key discoveries or decisions]
|
||||||
|
|
||||||
### Files Modified
|
### Files Modified
|
||||||
|
|
||||||
[List of files touched]
|
[List of files touched]
|
||||||
|
|
||||||
### Open Questions
|
### Open Questions
|
||||||
|
|
||||||
[Unresolved items for next agent]
|
[Unresolved items for next agent]
|
||||||
|
|
||||||
### Recommendations
|
### Recommendations
|
||||||
|
|
||||||
[Suggested next steps]
|
[Suggested next steps]
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -73,18 +86,21 @@ Between agents, create handoff document:
|
|||||||
Executes:
|
Executes:
|
||||||
|
|
||||||
1. **Planner Agent**
|
1. **Planner Agent**
|
||||||
|
|
||||||
- Analyzes requirements
|
- Analyzes requirements
|
||||||
- Creates implementation plan
|
- Creates implementation plan
|
||||||
- Identifies dependencies
|
- Identifies dependencies
|
||||||
- Output: `HANDOFF: planner -> tdd-guide`
|
- Output: `HANDOFF: planner -> tdd-guide`
|
||||||
|
|
||||||
2. **TDD Guide Agent**
|
2. **TDD Guide Agent**
|
||||||
|
|
||||||
- Reads planner handoff
|
- Reads planner handoff
|
||||||
- Writes tests first
|
- Writes tests first
|
||||||
- Implements to pass tests
|
- Implements to pass tests
|
||||||
- Output: `HANDOFF: tdd-guide -> code-reviewer`
|
- Output: `HANDOFF: tdd-guide -> code-reviewer`
|
||||||
|
|
||||||
3. **Code Reviewer Agent**
|
3. **Code Reviewer Agent**
|
||||||
|
|
||||||
- Reviews implementation
|
- Reviews implementation
|
||||||
- Checks for issues
|
- Checks for issues
|
||||||
- Suggests improvements
|
- Suggests improvements
|
||||||
@@ -139,18 +155,22 @@ For independent checks, run agents in parallel:
|
|||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
### Parallel Phase
|
### Parallel Phase
|
||||||
|
|
||||||
Run simultaneously:
|
Run simultaneously:
|
||||||
|
|
||||||
- code-reviewer (quality)
|
- code-reviewer (quality)
|
||||||
- security-reviewer (security)
|
- security-reviewer (security)
|
||||||
- architect (design)
|
- architect (design)
|
||||||
|
|
||||||
### Merge Results
|
### Merge Results
|
||||||
|
|
||||||
Combine outputs into single report
|
Combine outputs into single report
|
||||||
```
|
```
|
||||||
|
|
||||||
## Arguments
|
## Arguments
|
||||||
|
|
||||||
$ARGUMENTS:
|
$ARGUMENTS:
|
||||||
|
|
||||||
- `feature <description>` - Full feature workflow
|
- `feature <description>` - Full feature workflow
|
||||||
- `bugfix <description>` - Bug fix workflow
|
- `bugfix <description>` - Bug fix workflow
|
||||||
- `refactor <description>` - Refactoring workflow
|
- `refactor <description>` - Refactoring workflow
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ This command invokes the **planner** agent to create a comprehensive implementat
|
|||||||
## When to Use
|
## When to Use
|
||||||
|
|
||||||
Use `/plan` when:
|
Use `/plan` when:
|
||||||
|
|
||||||
- Starting a new feature
|
- Starting a new feature
|
||||||
- Making significant architectural changes
|
- Making significant architectural changes
|
||||||
- Working on complex refactoring
|
- Working on complex refactoring
|
||||||
@@ -82,6 +83,7 @@ Agent (planner):
|
|||||||
**CRITICAL**: The planner agent will **NOT** write any code until you explicitly confirm the plan with "yes" or "proceed" or similar affirmative response.
|
**CRITICAL**: The planner agent will **NOT** write any code until you explicitly confirm the plan with "yes" or "proceed" or similar affirmative response.
|
||||||
|
|
||||||
If you want changes, respond with:
|
If you want changes, respond with:
|
||||||
|
|
||||||
- "modify: [your changes]"
|
- "modify: [your changes]"
|
||||||
- "different approach: [alternative]"
|
- "different approach: [alternative]"
|
||||||
- "skip phase 2 and do phase 3 first"
|
- "skip phase 2 and do phase 3 first"
|
||||||
@@ -89,6 +91,7 @@ If you want changes, respond with:
|
|||||||
## Integration with Other Commands
|
## Integration with Other Commands
|
||||||
|
|
||||||
After planning:
|
After planning:
|
||||||
|
|
||||||
- Use `/tdd` to implement with test-driven development
|
- Use `/tdd` to implement with test-driven development
|
||||||
- Use `/build-and-fix` if build errors occur
|
- Use `/build-and-fix` if build errors occur
|
||||||
- Use `/code-review` to review completed implementation
|
- Use `/code-review` to review completed implementation
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
Safely identify and remove dead code with test verification:
|
Safely identify and remove dead code with test verification:
|
||||||
|
|
||||||
1. Run dead code analysis tools:
|
1. Run dead code analysis tools:
|
||||||
|
|
||||||
- knip: Find unused exports and files
|
- knip: Find unused exports and files
|
||||||
- depcheck: Find unused dependencies
|
- depcheck: Find unused dependencies
|
||||||
- ts-prune: Find unused TypeScript exports
|
- ts-prune: Find unused TypeScript exports
|
||||||
@@ -10,6 +11,7 @@ Safely identify and remove dead code with test verification:
|
|||||||
2. Generate comprehensive report in .reports/dead-code-analysis.md
|
2. Generate comprehensive report in .reports/dead-code-analysis.md
|
||||||
|
|
||||||
3. Categorize findings by severity:
|
3. Categorize findings by severity:
|
||||||
|
|
||||||
- SAFE: Test files, unused utilities
|
- SAFE: Test files, unused utilities
|
||||||
- CAUTION: API routes, components
|
- CAUTION: API routes, components
|
||||||
- DANGER: Config files, main entry points
|
- DANGER: Config files, main entry points
|
||||||
@@ -17,6 +19,7 @@ Safely identify and remove dead code with test verification:
|
|||||||
4. Propose safe deletions only
|
4. Propose safe deletions only
|
||||||
|
|
||||||
5. Before each deletion:
|
5. Before each deletion:
|
||||||
|
|
||||||
- Run full test suite
|
- Run full test suite
|
||||||
- Verify tests pass
|
- Verify tests pass
|
||||||
- Apply change
|
- Apply change
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ When determining which package manager to use, the following order is checked:
|
|||||||
## Configuration Files
|
## Configuration Files
|
||||||
|
|
||||||
### Global Configuration
|
### Global Configuration
|
||||||
|
|
||||||
```json
|
```json
|
||||||
// ~/.cursor/package-manager.json
|
// ~/.cursor/package-manager.json
|
||||||
{
|
{
|
||||||
@@ -45,6 +46,7 @@ When determining which package manager to use, the following order is checked:
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Project Configuration
|
### Project Configuration
|
||||||
|
|
||||||
```json
|
```json
|
||||||
// .cursor/package-manager.json
|
// .cursor/package-manager.json
|
||||||
{
|
{
|
||||||
@@ -53,6 +55,7 @@ When determining which package manager to use, the following order is checked:
|
|||||||
```
|
```
|
||||||
|
|
||||||
### package.json
|
### package.json
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"packageManager": "pnpm@8.6.0"
|
"packageManager": "pnpm@8.6.0"
|
||||||
|
|||||||
@@ -34,12 +34,12 @@ User: /tdd Add validation for the full-page name field
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// full-page.validator.test.ts
|
// full-page.validator.test.ts
|
||||||
import { createFullPageSchema } from './full-page.validator'
|
import { createFullPageSchema } from './full-page.validator';
|
||||||
|
|
||||||
it('rejects an empty name', () => {
|
it('rejects an empty name', () => {
|
||||||
const schema = createFullPageSchema((k) => k)
|
const schema = createFullPageSchema((k) => k);
|
||||||
expect(schema.safeParse({ code: 'ABC', name: '' }).success).toBe(false)
|
expect(schema.safeParse({ code: 'ABC', name: '' }).success).toBe(false);
|
||||||
})
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ Analyze Vitest coverage and add missing tests:
|
|||||||
2. Identify files below 80%
|
2. Identify files below 80%
|
||||||
|
|
||||||
3. For each under-covered file:
|
3. For each under-covered file:
|
||||||
|
|
||||||
- Unit tests for validators, transformers, utils, stores
|
- Unit tests for validators, transformers, utils, stores
|
||||||
- Testing Library tests for `@repo/ui` components
|
- Testing Library tests for `@repo/ui` components
|
||||||
- Journey tests for critical `apps/web` flows (mock data services)
|
- Journey tests for critical `apps/web` flows (mock data services)
|
||||||
@@ -16,6 +17,7 @@ Analyze Vitest coverage and add missing tests:
|
|||||||
5. Show before/after coverage
|
5. Show before/after coverage
|
||||||
|
|
||||||
Focus on:
|
Focus on:
|
||||||
|
|
||||||
- Happy path
|
- Happy path
|
||||||
- Error handling
|
- Error handling
|
||||||
- Edge cases (null, undefined, empty)
|
- Edge cases (null, undefined, empty)
|
||||||
|
|||||||
@@ -3,12 +3,15 @@
|
|||||||
Sync docs with this frontend monorepo. Source of truth: `package.json`, `apps/web/.env.example`, and `apps/docs-dev` (VitePress).
|
Sync docs with this frontend monorepo. Source of truth: `package.json`, `apps/web/.env.example`, and `apps/docs-dev` (VitePress).
|
||||||
|
|
||||||
1. Read root `package.json` scripts
|
1. Read root `package.json` scripts
|
||||||
|
|
||||||
- Table of `pnpm dev:web`, `pnpm dev:showcase`, `pnpm dev:docs-dev`, `pnpm typecheck:web`, `pnpm test`, `pnpm check:all`
|
- Table of `pnpm dev:web`, `pnpm dev:showcase`, `pnpm dev:docs-dev`, `pnpm typecheck:web`, `pnpm test`, `pnpm check:all`
|
||||||
|
|
||||||
2. Read `apps/web/.env.example`
|
2. Read `apps/web/.env.example`
|
||||||
|
|
||||||
- Document each `VITE_*` var (they are public to the client)
|
- Document each `VITE_*` var (they are public to the client)
|
||||||
|
|
||||||
3. Update `apps/docs-dev` (VitePress) and the root README
|
3. Update `apps/docs-dev` (VitePress) and the root README
|
||||||
|
|
||||||
- Where to work (`apps/web` vs `showcase`)
|
- Where to work (`apps/web` vs `showcase`)
|
||||||
- Module layout (`example/full-page`)
|
- Module layout (`example/full-page`)
|
||||||
- Preferred `@repo/*` imports
|
- Preferred `@repo/*` imports
|
||||||
|
|||||||
@@ -7,23 +7,28 @@ Run comprehensive verification on current codebase state.
|
|||||||
Execute verification in this exact order:
|
Execute verification in this exact order:
|
||||||
|
|
||||||
1. **Build Check**
|
1. **Build Check**
|
||||||
|
|
||||||
- Run the build command for this project
|
- Run the build command for this project
|
||||||
- If it fails, report errors and STOP
|
- If it fails, report errors and STOP
|
||||||
|
|
||||||
2. **Type Check**
|
2. **Type Check**
|
||||||
|
|
||||||
- Run TypeScript/type checker
|
- Run TypeScript/type checker
|
||||||
- Report all errors with file:line
|
- Report all errors with file:line
|
||||||
|
|
||||||
3. **Lint Check**
|
3. **Lint Check**
|
||||||
|
|
||||||
- Run linter
|
- Run linter
|
||||||
- Report warnings and errors
|
- Report warnings and errors
|
||||||
|
|
||||||
4. **Test Suite**
|
4. **Test Suite**
|
||||||
|
|
||||||
- Run all tests
|
- Run all tests
|
||||||
- Report pass/fail count
|
- Report pass/fail count
|
||||||
- Report coverage percentage
|
- Report coverage percentage
|
||||||
|
|
||||||
5. **Console.log Audit**
|
5. **Console.log Audit**
|
||||||
|
|
||||||
- Search for console.log in source files
|
- Search for console.log in source files
|
||||||
- Report locations
|
- Report locations
|
||||||
|
|
||||||
@@ -53,6 +58,7 @@ If any critical issues, list them with fix suggestions.
|
|||||||
## Arguments
|
## Arguments
|
||||||
|
|
||||||
$ARGUMENTS can be:
|
$ARGUMENTS can be:
|
||||||
|
|
||||||
- `quick` - Only build + types
|
- `quick` - Only build + types
|
||||||
- `full` - All checks (default)
|
- `full` - All checks (default)
|
||||||
- `pre-commit` - Checks relevant for commits
|
- `pre-commit` - Checks relevant for commits
|
||||||
|
|||||||
@@ -4,17 +4,20 @@ Mode: Active development
|
|||||||
Focus: Implementation, coding, building features
|
Focus: Implementation, coding, building features
|
||||||
|
|
||||||
## Behavior
|
## Behavior
|
||||||
|
|
||||||
- Write code first, explain after
|
- Write code first, explain after
|
||||||
- Prefer working solutions over perfect solutions
|
- Prefer working solutions over perfect solutions
|
||||||
- Run tests after changes
|
- Run tests after changes
|
||||||
- Keep commits atomic
|
- Keep commits atomic
|
||||||
|
|
||||||
## Priorities
|
## Priorities
|
||||||
|
|
||||||
1. Get it working
|
1. Get it working
|
||||||
2. Get it right
|
2. Get it right
|
||||||
3. Get it clean
|
3. Get it clean
|
||||||
|
|
||||||
## Tools to favor
|
## Tools to favor
|
||||||
|
|
||||||
- Edit, Write for code changes
|
- Edit, Write for code changes
|
||||||
- Bash for running tests/builds
|
- Bash for running tests/builds
|
||||||
- Grep, Glob for finding code
|
- Grep, Glob for finding code
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ Mode: Exploration, investigation, learning
|
|||||||
Focus: Understanding before acting
|
Focus: Understanding before acting
|
||||||
|
|
||||||
## Behavior
|
## Behavior
|
||||||
|
|
||||||
- Read widely before concluding
|
- Read widely before concluding
|
||||||
- Ask clarifying questions
|
- Ask clarifying questions
|
||||||
- Document findings as you go
|
- Document findings as you go
|
||||||
- Don't write code until understanding is clear
|
- Don't write code until understanding is clear
|
||||||
|
|
||||||
## Research Process
|
## Research Process
|
||||||
|
|
||||||
1. Understand the question
|
1. Understand the question
|
||||||
2. Explore relevant code/docs
|
2. Explore relevant code/docs
|
||||||
3. Form hypothesis
|
3. Form hypothesis
|
||||||
@@ -17,10 +19,12 @@ Focus: Understanding before acting
|
|||||||
5. Summarize findings
|
5. Summarize findings
|
||||||
|
|
||||||
## Tools to favor
|
## Tools to favor
|
||||||
|
|
||||||
- Read for understanding code
|
- Read for understanding code
|
||||||
- Grep, Glob for finding patterns
|
- Grep, Glob for finding patterns
|
||||||
- WebSearch, WebFetch for external docs
|
- WebSearch, WebFetch for external docs
|
||||||
- Task with Explore agent for codebase questions
|
- Task with Explore agent for codebase questions
|
||||||
|
|
||||||
## Output
|
## Output
|
||||||
|
|
||||||
Findings first, recommendations second
|
Findings first, recommendations second
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ Mode: PR review, code analysis
|
|||||||
Focus: Quality, security, maintainability
|
Focus: Quality, security, maintainability
|
||||||
|
|
||||||
## Behavior
|
## Behavior
|
||||||
|
|
||||||
- Read thoroughly before commenting
|
- Read thoroughly before commenting
|
||||||
- Prioritize issues by severity (critical > high > medium > low)
|
- Prioritize issues by severity (critical > high > medium > low)
|
||||||
- Suggest fixes, don't just point out problems
|
- Suggest fixes, don't just point out problems
|
||||||
- Check for security vulnerabilities
|
- Check for security vulnerabilities
|
||||||
|
|
||||||
## Review Checklist
|
## Review Checklist
|
||||||
|
|
||||||
- [ ] Logic errors
|
- [ ] Logic errors
|
||||||
- [ ] Edge cases
|
- [ ] Edge cases
|
||||||
- [ ] Error handling
|
- [ ] Error handling
|
||||||
@@ -19,4 +21,5 @@ Focus: Quality, security, maintainability
|
|||||||
- [ ] Test coverage
|
- [ ] Test coverage
|
||||||
|
|
||||||
## Output Format
|
## Output Format
|
||||||
|
|
||||||
Group findings by file, severity first
|
Group findings by file, severity first
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
# Dependencies
|
# Dependencies
|
||||||
node_modules
|
node_modules
|
||||||
.pnpm-store
|
.pnpm-store
|
||||||
|
.cursor/sessions/*
|
||||||
|
|
||||||
# Turborepo & Cache
|
# Turborepo & Cache
|
||||||
.turbo
|
.turbo
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
# Desktop Auto-Update System
|
# Desktop Auto-Update System
|
||||||
|
|
||||||
> **Architectural Foundation:** [electron-updater](https://www.npmjs.com/package/electron-updater) · [electron-builder](https://www.electron.build/) · [GitHub Actions](https://docs.github.com/en/actions)
|
> **Architectural Foundation:** [electron-updater](https://www.npmjs.com/package/electron-updater) · [electron-builder](https://www.electron.build/) · [GitHub Actions](https://docs.github.com/en/actions)
|
||||||
@@ -113,8 +112,7 @@ pnpm run prebuild
|
|||||||
GH_TOKEN=your_token electron-builder --publish always --config electron-builder.yml
|
GH_TOKEN=your_token electron-builder --publish always --config electron-builder.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
> [!CAUTION]
|
> [!CAUTION] > **Treat `GH_TOKEN` as a critical secret.** It grants write access to your repository's release assets. Never commit it to version control, never log it in CI output, and always inject it via encrypted secrets or a vault.
|
||||||
> **Treat `GH_TOKEN` as a critical secret.** It grants write access to your repository's release assets. Never commit it to version control, never log it in CI output, and always inject it via encrypted secrets or a vault.
|
|
||||||
|
|
||||||
### Automated Release (GitHub Actions)
|
### Automated Release (GitHub Actions)
|
||||||
|
|
||||||
@@ -232,8 +230,7 @@ publish:
|
|||||||
|
|
||||||
Your server must host the same directory structure as the S3 layout above.
|
Your server must host the same directory structure as the S3 layout above.
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT] > **MIME Type Configuration**: Ensure your file server correctly serves `.yml` files with `text/yaml` and installer binaries with `application/octet-stream`. Incorrect MIME types will cause download corruption or silent update failures.
|
||||||
> **MIME Type Configuration**: Ensure your file server correctly serves `.yml` files with `text/yaml` and installer binaries with `application/octet-stream`. Incorrect MIME types will cause download corruption or silent update failures.
|
|
||||||
|
|
||||||
**Nginx reference:**
|
**Nginx reference:**
|
||||||
|
|
||||||
@@ -260,8 +257,7 @@ server {
|
|||||||
|
|
||||||
## 🛡️ Code Signing: The Trust Boundary
|
## 🛡️ Code Signing: The Trust Boundary
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING] > **Code signing is not merely a requirement — it is the Trust Boundary established by the operating system.** macOS Gatekeeper will explicitly terminate unsigned applications or refuse background updates to maintain system integrity. Windows SmartScreen will display alarming warnings to users. Without valid signatures, `electron-updater` will **reject update payloads entirely**.
|
||||||
> **Code signing is not merely a requirement — it is the Trust Boundary established by the operating system.** macOS Gatekeeper will explicitly terminate unsigned applications or refuse background updates to maintain system integrity. Windows SmartScreen will display alarming warnings to users. Without valid signatures, `electron-updater` will **reject update payloads entirely**.
|
|
||||||
|
|
||||||
### macOS
|
### macOS
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
# Desktop Configuration Guide
|
# Desktop Configuration Guide
|
||||||
|
|
||||||
> **Architectural Foundation:** [Electron Protocol API](https://www.electronjs.org/docs/latest/api/protocol) · [electron-builder](https://www.electron.build/) · [React Router](https://reactrouter.com/)
|
> **Architectural Foundation:** [Electron Protocol API](https://www.electronjs.org/docs/latest/api/protocol) · [electron-builder](https://www.electron.build/) · [React Router](https://reactrouter.com/)
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ outline: [2, 3]
|
|||||||
>
|
>
|
||||||
> **Description:** Hardened IPC security model enforcing privilege separation via contextBridge, defining the Three-Step Bridge SOP for native feature exposure, the verified channel manifest, and critical anti-pattern audit checklist.
|
> **Description:** Hardened IPC security model enforcing privilege separation via contextBridge, defining the Three-Step Bridge SOP for native feature exposure, the verified channel manifest, and critical anti-pattern audit checklist.
|
||||||
|
|
||||||
|
|
||||||
> **Scope**: [Electron](https://www.electronjs.org/) Main ↔ Renderer process communication
|
> **Scope**: [Electron](https://www.electronjs.org/) Main ↔ Renderer process communication
|
||||||
>
|
>
|
||||||
> **Enforcement Level**: Mandatory — deviations constitute security violations
|
> **Enforcement Level**: Mandatory — deviations constitute security violations
|
||||||
@@ -18,7 +17,7 @@ This document defines the **hardened security perimeter** and communication topo
|
|||||||
The architecture operates on three invariants:
|
The architecture operates on three invariants:
|
||||||
|
|
||||||
| Invariant | Guarantee |
|
| Invariant | Guarantee |
|
||||||
|---|---|
|
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| **Context Encapsulation** | The Preload Script executes in a hermetically sealed V8 context, isolated from both Main Process globals and the Renderer DOM. |
|
| **Context Encapsulation** | The Preload Script executes in a hermetically sealed V8 context, isolated from both Main Process globals and the Renderer DOM. |
|
||||||
| **Interface Narrowing** | Only explicitly declared, type-safe API surfaces are exposed via `contextBridge`. No wildcard access patterns exist. |
|
| **Interface Narrowing** | Only explicitly declared, type-safe API surfaces are exposed via `contextBridge`. No wildcard access patterns exist. |
|
||||||
| **Deterministic Lifecycle** | All IPC subscriptions are paired with unsubscribe functions, tying native event listeners to React's component lifecycle to prevent memory leaks. |
|
| **Deterministic Lifecycle** | All IPC subscriptions are paired with unsubscribe functions, tying native event listeners to React's component lifecycle to prevent memory leaks. |
|
||||||
@@ -101,7 +100,7 @@ The Preload Script functions as a **Secure Gateway** that performs **Interface N
|
|||||||
These settings are declared in `BrowserWindow.webPreferences` and are **non-negotiable**:
|
These settings are declared in `BrowserWindow.webPreferences` and are **non-negotiable**:
|
||||||
|
|
||||||
| Setting | Value | Enforcement |
|
| Setting | Value | Enforcement |
|
||||||
| ------------------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ------------------ | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `contextIsolation` | `true` | The Preload executes in a hermetically sealed V8 context. The renderer **cannot** access `require()`, Node.js globals, or any variable from the preload's scope. |
|
| `contextIsolation` | `true` | The Preload executes in a hermetically sealed V8 context. The renderer **cannot** access `require()`, Node.js globals, or any variable from the preload's scope. |
|
||||||
| `nodeIntegration` | `false` | **Zero** Node.js API surface in the renderer. `fs`, `child_process`, `os`, `net`, and all built-in modules are completely unavailable. |
|
| `nodeIntegration` | `false` | **Zero** Node.js API surface in the renderer. `fs`, `child_process`, `os`, `net`, and all built-in modules are completely unavailable. |
|
||||||
| `sandbox` | `true` | The renderer process runs inside a **[Chromium OS-level sandbox](https://chromium.googlesource.com/chromium/src/+/HEAD/docs/design/sandbox.md)**, restricting system calls and file access at the kernel level. |
|
| `sandbox` | `true` | The renderer process runs inside a **[Chromium OS-level sandbox](https://chromium.googlesource.com/chromium/src/+/HEAD/docs/design/sandbox.md)**, restricting system calls and file access at the kernel level. |
|
||||||
@@ -113,8 +112,7 @@ These settings are declared in `BrowserWindow.webPreferences` and are **non-nego
|
|||||||
|
|
||||||
Every native feature in this architecture **must** follow the Three-Step Bridge — a Standard Operating Procedure (SOP) that ensures traceability, type-safety, and auditability across the entire IPC surface.
|
Every native feature in this architecture **must** follow the Three-Step Bridge — a Standard Operating Procedure (SOP) that ensures traceability, type-safety, and auditability across the entire IPC surface.
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT] > **Deterministic Synchronization**: Maintaining parity between the Main Process handler, the Preload Gateway exposure, and the TypeScript interface declaration is **mandatory**. A mismatch between any two of the three layers will result in either a **Type-Safety Gap** (silent failures in development) or a **Runtime Regression** (crashes in production).
|
||||||
> **Deterministic Synchronization**: Maintaining parity between the Main Process handler, the Preload Gateway exposure, and the TypeScript interface declaration is **mandatory**. A mismatch between any two of the three layers will result in either a **Type-Safety Gap** (silent failures in development) or a **Runtime Regression** (crashes in production).
|
|
||||||
|
|
||||||
### Step 1: Register the Handler — Main Process
|
### Step 1: Register the Handler — Main Process
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
# Desktop
|
# Desktop
|
||||||
|
|
||||||
> **Architectural Foundation:** [Electron](https://www.electronjs.org/) · [electron-vite](https://electron-vite.org/) · [electron-builder](https://www.electron.build/) · [electron-updater](https://www.npmjs.com/package/electron-updater)
|
> **Architectural Foundation:** [Electron](https://www.electronjs.org/) · [electron-vite](https://electron-vite.org/) · [electron-builder](https://www.electron.build/) · [electron-updater](https://www.npmjs.com/package/electron-updater)
|
||||||
>
|
>
|
||||||
> **Description:** Secure Electron desktop wrapper that embeds monorepo web applications, providing custom app:// protocol routing, hardware IPC bridge, auto-updates, and CORS bypass proxy with hardened security defaults.
|
> **Description:** Secure Electron desktop wrapper that embeds monorepo web applications, providing custom app:// protocol routing, hardware IPC bridge, auto-updates, and CORS bypass proxy with hardened security defaults.
|
||||||
@@ -32,7 +33,7 @@ pnpm package:desktop
|
|||||||
## How It Works
|
## How It Works
|
||||||
|
|
||||||
| Environment | Operational Logic |
|
| Environment | Operational Logic |
|
||||||
|---|---|
|
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| **Development** | Bridges the Electron shell with the Vite Dev Server, enabling Hot Module Replacement (HMR) and real-time UI synchronization at `http://localhost:5173`. |
|
| **Development** | Bridges the Electron shell with the Vite Dev Server, enabling Hot Module Replacement (HMR) and real-time UI synchronization at `http://localhost:5173`. |
|
||||||
| **Production** | Orchestrates a Secure Custom Protocol (`app://`) to serve optimized static assets, ensuring seamless SPA client-side routing via an intelligent `index.html` fallback mechanism. |
|
| **Production** | Orchestrates a Secure Custom Protocol (`app://`) to serve optimized static assets, ensuring seamless SPA client-side routing via an intelligent `index.html` fallback mechanism. |
|
||||||
|
|
||||||
@@ -72,7 +73,7 @@ apps/desktop/
|
|||||||
### Development & Build
|
### Development & Build
|
||||||
|
|
||||||
| Script | Description |
|
| Script | Description |
|
||||||
|---|---|
|
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `pnpm dev` | Launch the electron-vite development server with live reload |
|
| `pnpm dev` | Launch the electron-vite development server with live reload |
|
||||||
| `pnpm build` | Compile main, preload, and renderer TypeScript modules → `out/` |
|
| `pnpm build` | Compile main, preload, and renderer TypeScript modules → `out/` |
|
||||||
| `pnpm prebuild` | Synchronize the target web app's build output via `scripts/copy-web-dist.ts` — copies `apps/<DESKTOP_TARGET_APP>/dist/` → `web-dist/`. Invoked automatically before `pnpm build`. |
|
| `pnpm prebuild` | Synchronize the target web app's build output via `scripts/copy-web-dist.ts` — copies `apps/<DESKTOP_TARGET_APP>/dist/` → `web-dist/`. Invoked automatically before `pnpm build`. |
|
||||||
@@ -83,7 +84,7 @@ apps/desktop/
|
|||||||
To generate a production-ready installer, execute from **within `apps/desktop/`** or use the root-level `pnpm package:*` commands, which orchestrate the full pipeline automatically:
|
To generate a production-ready installer, execute from **within `apps/desktop/`** or use the root-level `pnpm package:*` commands, which orchestrate the full pipeline automatically:
|
||||||
|
|
||||||
| Command | Platform | Output Artifact |
|
| Command | Platform | Output Artifact |
|
||||||
|---|---|---|
|
| -------------------- | ---------- | ---------------------------------------- |
|
||||||
| `pnpm package` | Current OS | Detects host OS and builds accordingly |
|
| `pnpm package` | Current OS | Detects host OS and builds accordingly |
|
||||||
| `pnpm package:mac` | macOS | `.dmg` and `.zip` (supports x64 & arm64) |
|
| `pnpm package:mac` | macOS | `.dmg` and `.zip` (supports x64 & arm64) |
|
||||||
| `pnpm package:win` | Windows | `.exe` (NSIS Installer) |
|
| `pnpm package:win` | Windows | `.exe` (NSIS Installer) |
|
||||||
@@ -91,13 +92,12 @@ To generate a production-ready installer, execute from **within `apps/desktop/`*
|
|||||||
|
|
||||||
All artifacts are emitted to the `release/` directory.
|
All artifacts are emitted to the `release/` directory.
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT] > **Deterministic Build Pipeline**: All `package:*` commands strictly enforce a deterministic build pipeline: compiling web assets via Turborepo, synchronizing the output via the `prebuild` bridge (`node --import tsx scripts/copy-web-dist.ts`), and finally generating the native binary through `electron-builder`.
|
||||||
> **Deterministic Build Pipeline**: All `package:*` commands strictly enforce a deterministic build pipeline: compiling web assets via Turborepo, synchronizing the output via the `prebuild` bridge (`node --import tsx scripts/copy-web-dist.ts`), and finally generating the native binary through `electron-builder`.
|
|
||||||
>
|
>
|
||||||
> **Running locally within `apps/desktop/`**: These scripts assume the web app has already been compiled. Either run `pnpm build --filter=web` beforehand, or use the root-level `pnpm package:*` commands which handle the complete orchestration.
|
> **Running locally within `apps/desktop/`**: These scripts assume the web app has already been compiled. Either run `pnpm build --filter=web` beforehand, or use the root-level `pnpm package:*` commands which handle the complete orchestration.
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING] > **macOS Code Signing**: Distributable macOS builds with Auto-Update capability **require** an Apple Developer Certificate. Provide the following environment variables:
|
||||||
> **macOS Code Signing**: Distributable macOS builds with Auto-Update capability **require** an Apple Developer Certificate. Provide the following environment variables:
|
>
|
||||||
> ```bash
|
> ```bash
|
||||||
> CSC_LINK=<base64-encoded .p12 certificate>
|
> CSC_LINK=<base64-encoded .p12 certificate>
|
||||||
> CSC_KEY_PASSWORD=<certificate password>
|
> CSC_KEY_PASSWORD=<certificate password>
|
||||||
@@ -105,10 +105,11 @@ All artifacts are emitted to the `release/` directory.
|
|||||||
> APPLE_APP_SPECIFIC_PASSWORD=<app-specific password>
|
> APPLE_APP_SPECIFIC_PASSWORD=<app-specific password>
|
||||||
> APPLE_TEAM_ID=<team id>
|
> APPLE_TEAM_ID=<team id>
|
||||||
> ```
|
> ```
|
||||||
|
>
|
||||||
> Without valid code signing, macOS Gatekeeper will quarantine the application and `electron-updater` will reject update payloads. See [AUTO_UPDATER.md](./AUTO_UPDATER.md) for the complete requirements.
|
> Without valid code signing, macOS Gatekeeper will quarantine the application and `electron-updater` will reject update payloads. See [AUTO_UPDATER.md](./AUTO_UPDATER.md) for the complete requirements.
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE] > **Cross-Compilation Advisory**: It is strongly recommended to build for each platform on its native OS. Cross-compilation (e.g., producing `.dmg` on Linux) may fail due to platform-specific toolchain dependencies. For CI, leverage a matrix strategy:
|
||||||
> **Cross-Compilation Advisory**: It is strongly recommended to build for each platform on its native OS. Cross-compilation (e.g., producing `.dmg` on Linux) may fail due to platform-specific toolchain dependencies. For CI, leverage a matrix strategy:
|
>
|
||||||
> ```yaml
|
> ```yaml
|
||||||
> strategy:
|
> strategy:
|
||||||
> matrix:
|
> matrix:
|
||||||
@@ -163,7 +164,7 @@ The application enforces a **single running instance** via `app.requestSingleIns
|
|||||||
The Desktop Wrapper enforces a **hardened security perimeter**, strictly isolating the Node.js Main Process from the Renderer Context. Our architecture is built upon the principle of **Least Privilege**, ensuring that the web application only interacts with system hardware through a verified, secure IPC bridge.
|
The Desktop Wrapper enforces a **hardened security perimeter**, strictly isolating the Node.js Main Process from the Renderer Context. Our architecture is built upon the principle of **Least Privilege**, ensuring that the web application only interacts with system hardware through a verified, secure IPC bridge.
|
||||||
|
|
||||||
| Setting | Value | Purpose |
|
| Setting | Value | Purpose |
|
||||||
|---|---|---|
|
| ------------------ | ------- | ------------------------------------------------------------ |
|
||||||
| `contextIsolation` | `true` | Preload executes in a hermetically sealed JavaScript context |
|
| `contextIsolation` | `true` | Preload executes in a hermetically sealed JavaScript context |
|
||||||
| `nodeIntegration` | `false` | Zero Node.js API surface exposed to the renderer |
|
| `nodeIntegration` | `false` | Zero Node.js API surface exposed to the renderer |
|
||||||
| `sandbox` | `true` | Chromium OS-level sandbox enforced |
|
| `sandbox` | `true` | Chromium OS-level sandbox enforced |
|
||||||
@@ -183,7 +184,7 @@ See [IPC_ARCHITECTURE.md](./IPC_ARCHITECTURE.md) for the full security model, th
|
|||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
| Document | Scope |
|
| Document | Scope |
|
||||||
|---|---|
|
| -------------------------------------------- | ------------------------------------------------------------------------------------- |
|
||||||
| [CONFIGURATION.md](./CONFIGURATION.md) | Target app switching, `app://` protocol internals, HashRouter fallback procedure |
|
| [CONFIGURATION.md](./CONFIGURATION.md) | Target app switching, `app://` protocol internals, HashRouter fallback procedure |
|
||||||
| [AUTO_UPDATER.md](./AUTO_UPDATER.md) | Release lifecycle, CI/CD variables, provider switching, code signing |
|
| [AUTO_UPDATER.md](./AUTO_UPDATER.md) | Release lifecycle, CI/CD variables, provider switching, code signing |
|
||||||
| [IPC_ARCHITECTURE.md](./IPC_ARCHITECTURE.md) | Security model, Three-Step Bridge pattern, existing IPC channels, extensibility guide |
|
| [IPC_ARCHITECTURE.md](./IPC_ARCHITECTURE.md) | Security model, Three-Step Bridge pattern, existing IPC channels, extensibility guide |
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
layout: home
|
layout: home
|
||||||
|
|
||||||
hero:
|
hero:
|
||||||
name: "Frontend Architecture"
|
name: 'Frontend Architecture'
|
||||||
text: "Enterprise Monorepo"
|
text: 'Enterprise Monorepo'
|
||||||
tagline: "A scalable, standardized foundation for Web & Desktop applications. Built for performance, consistency, and velocity."
|
tagline: 'A scalable, standardized foundation for Web & Desktop applications. Built for performance, consistency, and velocity.'
|
||||||
actions:
|
actions:
|
||||||
- theme: brand
|
- theme: brand
|
||||||
text: Get Started
|
text: Get Started
|
||||||
@@ -31,6 +31,7 @@ features:
|
|||||||
link: /packages/core-api/
|
link: /packages/core-api/
|
||||||
linkText: Explore Core
|
linkText: Explore Core
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="custom-divider"></div>
|
<div class="custom-divider"></div>
|
||||||
<div class="bento-container">
|
<div class="bento-container">
|
||||||
<div class="bento-header">
|
<div class="bento-header">
|
||||||
|
|||||||
@@ -34,36 +34,38 @@ The monorepo is organized into **Apps** (deployable applications) and **Packages
|
|||||||
## 📦 Packages Overview
|
## 📦 Packages Overview
|
||||||
|
|
||||||
### 1. `apps/web`
|
### 1. `apps/web`
|
||||||
|
|
||||||
The main consumer-facing application.
|
The main consumer-facing application.
|
||||||
|
|
||||||
* Imports business logic from `@repo/utils`
|
- Imports business logic from `@repo/utils`
|
||||||
* Uses shared UI components from `@repo/ui`
|
- Uses shared UI components from `@repo/ui`
|
||||||
|
|
||||||
**Tech Stack**:
|
**Tech Stack**:
|
||||||
|
|
||||||
* [React](https://react.dev/)
|
- [React](https://react.dev/)
|
||||||
* [Vite](https://vite.dev/)
|
- [Vite](https://vite.dev/)
|
||||||
* [TypeScript](https://www.typescriptlang.org/)
|
- [TypeScript](https://www.typescriptlang.org/)
|
||||||
* [Tailwind CSS](https://tailwindcss.com/)
|
- [Tailwind CSS](https://tailwindcss.com/)
|
||||||
|
|
||||||
### 2. `apps/desktop`
|
### 2. `apps/desktop`
|
||||||
|
|
||||||
The **Electron desktop wrapper** that embeds `apps/web` for native desktop experiences.
|
The **Electron desktop wrapper** that embeds `apps/web` for native desktop experiences.
|
||||||
|
|
||||||
* In **development**: loads the Vite dev server with full hot reload
|
- In **development**: loads the Vite dev server with full hot reload
|
||||||
* In **production**: serves the static web build via a secure custom `app://` protocol
|
- In **production**: serves the static web build via a secure custom `app://` protocol
|
||||||
* Configurable target app via `.env` (can wrap `apps/web`, `apps/docs-dev`, or any future app)
|
- Configurable target app via `.env` (can wrap `apps/web`, `apps/docs-dev`, or any future app)
|
||||||
|
|
||||||
**Tech Stack**:
|
**Tech Stack**:
|
||||||
|
|
||||||
* [Electron](https://www.electronjs.org/) 33.x
|
- [Electron](https://www.electronjs.org/) 33.x
|
||||||
* [electron-vite](https://electron-vite.org/)
|
- [electron-vite](https://electron-vite.org/)
|
||||||
* [electron-builder](https://www.electron.build/)
|
- [electron-builder](https://www.electron.build/)
|
||||||
* [electron-updater](https://www.npmjs.com/package/electron-updater)
|
- [electron-updater](https://www.npmjs.com/package/electron-updater)
|
||||||
|
|
||||||
**Key Capabilities**:
|
**Key Capabilities**:
|
||||||
|
|
||||||
| Feature | Description |
|
| Feature | Description |
|
||||||
|---|---|
|
| -------------------- | ---------------------------------------------------------------------- |
|
||||||
| 🖨️ Native Printing | Silent and direct printing via secure IPC bridge |
|
| 🖨️ Native Printing | Silent and direct printing via secure IPC bridge |
|
||||||
| 🔄 Auto-Updates | Background downloads via GitHub Releases (switchable to S3) |
|
| 🔄 Auto-Updates | Background downloads via GitHub Releases (switchable to S3) |
|
||||||
| 🔒 Secure IPC Bridge | `contextIsolation: true`, `nodeIntegration: false`, `sandbox: true` |
|
| 🔒 Secure IPC Bridge | `contextIsolation: true`, `nodeIntegration: false`, `sandbox: true` |
|
||||||
@@ -71,43 +73,47 @@ The **Electron desktop wrapper** that embeds `apps/web` for native desktop exper
|
|||||||
| 🛡️ CORS Bypass | Transparent Origin header rewriting for cloud API calls |
|
| 🛡️ CORS Bypass | Transparent Origin header rewriting for cloud API calls |
|
||||||
|
|
||||||
### 3. `apps/landing`
|
### 3. `apps/landing`
|
||||||
|
|
||||||
The **public promotional website** — a standalone SPA for the company profile and marketing pages.
|
The **public promotional website** — a standalone SPA for the company profile and marketing pages.
|
||||||
|
|
||||||
* Deployed independently to the web (e.g., [Vercel](https://vercel.com/)) — no interaction with Electron
|
- Deployed independently to the web (e.g., [Vercel](https://vercel.com/)) — no interaction with Electron
|
||||||
* Consumes shared UI components from `@repo/ui` and utilities from `@repo/utils`
|
- Consumes shared UI components from `@repo/ui` and utilities from `@repo/utils`
|
||||||
* Locked to port **3000** (`strictPort: true`) — evacuated from the `517x` range to avoid `electron-vite` port collisions
|
- Locked to port **3000** (`strictPort: true`) — evacuated from the `517x` range to avoid `electron-vite` port collisions
|
||||||
|
|
||||||
**Tech Stack**:
|
**Tech Stack**:
|
||||||
|
|
||||||
* [React](https://react.dev/)
|
- [React](https://react.dev/)
|
||||||
* [Vite](https://vite.dev/)
|
- [Vite](https://vite.dev/)
|
||||||
* [TypeScript](https://www.typescriptlang.org/)
|
- [TypeScript](https://www.typescriptlang.org/)
|
||||||
* [Tailwind CSS](https://tailwindcss.com/) v4
|
- [Tailwind CSS](https://tailwindcss.com/) v4
|
||||||
|
|
||||||
### 4. `apps/docs-dev`
|
### 4. `apps/docs-dev`
|
||||||
|
|
||||||
An isolated environment for developing and documenting UI components.
|
An isolated environment for developing and documenting UI components.
|
||||||
* Ensures components in `@repo/ui` are built and tested independently
|
|
||||||
* Acts as a living design system and playground
|
- Ensures components in `@repo/ui` are built and tested independently
|
||||||
* Built with **[VitePress](https://vitepress.dev/)**
|
- Acts as a living design system and playground
|
||||||
|
- Built with **[VitePress](https://vitepress.dev/)**
|
||||||
|
|
||||||
### 5. `packages/core-api`
|
### 5. `packages/core-api`
|
||||||
|
|
||||||
The **platform-agnostic API engine** for the monorepo. Provides an isolated HTTP client factory, a unified observability pipeline ([Grafana Faro](https://grafana.com/docs/grafana-cloud/monitor-applications/frontend-observability/faro-web-sdk/) + [OpenTelemetry](https://opentelemetry.io/)), and a generic data services engine.
|
The **platform-agnostic API engine** for the monorepo. Provides an isolated HTTP client factory, a unified observability pipeline ([Grafana Faro](https://grafana.com/docs/grafana-cloud/monitor-applications/frontend-observability/faro-web-sdk/) + [OpenTelemetry](https://opentelemetry.io/)), and a generic data services engine.
|
||||||
|
|
||||||
* Consumed by `apps/web`, `apps/landing`, and any future workspace
|
- Consumed by `apps/web`, `apps/landing`, and any future workspace
|
||||||
* Centralizes all `@grafana/faro-*` and `@opentelemetry/*` dependencies
|
- Centralizes all `@grafana/faro-*` and `@opentelemetry/*` dependencies
|
||||||
* Provides plug-and-play telemetry via `initTelemetry()` + `faroAdapter`
|
- Provides plug-and-play telemetry via `initTelemetry()` + `faroAdapter`
|
||||||
|
|
||||||
**Tech Stack**:
|
**Tech Stack**:
|
||||||
|
|
||||||
* [Axios](https://axios-http.com/) (isolated instances, zero singleton pollution)
|
- [Axios](https://axios-http.com/) (isolated instances, zero singleton pollution)
|
||||||
* [Grafana Faro](https://grafana.com/docs/grafana-cloud/monitor-applications/frontend-observability/faro-web-sdk/) (RUM, Logs, Error tracking)
|
- [Grafana Faro](https://grafana.com/docs/grafana-cloud/monitor-applications/frontend-observability/faro-web-sdk/) (RUM, Logs, Error tracking)
|
||||||
* [OpenTelemetry](https://opentelemetry.io/) (custom spans, distributed tracing)
|
- [OpenTelemetry](https://opentelemetry.io/) (custom spans, distributed tracing)
|
||||||
* [TypeScript](https://www.typescriptlang.org/) (strict types, module augmentation)
|
- [TypeScript](https://www.typescriptlang.org/) (strict types, module augmentation)
|
||||||
|
|
||||||
**Key Capabilities**:
|
**Key Capabilities**:
|
||||||
|
|
||||||
| Feature | Description |
|
| Feature | Description |
|
||||||
|---|---|
|
| ------------------------ | ---------------------------------------------------------------------------------------- |
|
||||||
| 🏭 HTTP Client Factory | `createHttpClient()` — per-app isolated Axios instances with interceptor hooks |
|
| 🏭 HTTP Client Factory | `createHttpClient()` — per-app isolated Axios instances with interceptor hooks |
|
||||||
| 📡 Faro/Loki Baseline | Every request automatically pushes structured logs with `module.key` and `module.action` |
|
| 📡 Faro/Loki Baseline | Every request automatically pushes structured logs with `module.key` and `module.action` |
|
||||||
| 🎯 Custom Spans (Opt-In) | `telemetryContext.customSpanName` creates explicit OTel spans visible in Grafana Tempo |
|
| 🎯 Custom Spans (Opt-In) | `telemetryContext.customSpanName` creates explicit OTel spans visible in Grafana Tempo |
|
||||||
@@ -115,10 +121,12 @@ The **platform-agnostic API engine** for the monorepo. Provides an isolated HTTP
|
|||||||
| 📦 Data Services Engine | `CommonRemoteDataServices` — full CRUD + lifecycle operations with zero boilerplate |
|
| 📦 Data Services Engine | `CommonRemoteDataServices` — full CRUD + lifecycle operations with zero boilerplate |
|
||||||
|
|
||||||
### 6. `packages/core-storage`
|
### 6. `packages/core-storage`
|
||||||
|
|
||||||
The **Enterprise-grade storage engine** for the monorepo.
|
The **Enterprise-grade storage engine** for the monorepo.
|
||||||
Provides a unified, Promise-based interface for interacting with browser storage (`localStorage` and `IndexedDB`). Enforces strict type safety, prevents key collisions via a centralized registry, and automatically provides **AES encryption at rest** for sensitive payloads using `@repo/utils`.
|
Provides a unified, Promise-based interface for interacting with browser storage (`localStorage` and `IndexedDB`). Enforces strict type safety, prevents key collisions via a centralized registry, and automatically provides **AES encryption at rest** for sensitive payloads using `@repo/utils`.
|
||||||
|
|
||||||
### 7. `packages/core-i18n`
|
### 7. `packages/core-i18n`
|
||||||
|
|
||||||
The **Enterprise Internationalization Architecture** for the monorepo.
|
The **Enterprise Internationalization Architecture** for the monorepo.
|
||||||
|
|
||||||
Provides a Hybrid Namespace Architecture combining a centralized i18n engine with decentralized, lazy-loaded feature dictionaries. Features strict TypeScript typings (including nested keys), optional backend synchronization with automatic error rollbacks, and a deep-merge mechanism for dynamic tenant-specific vocabulary overrides.
|
Provides a Hybrid Namespace Architecture combining a centralized i18n engine with decentralized, lazy-loaded feature dictionaries. Features strict TypeScript typings (including nested keys), optional backend synchronization with automatic error rollbacks, and a deep-merge mechanism for dynamic tenant-specific vocabulary overrides.
|
||||||
@@ -126,13 +134,14 @@ Provides a Hybrid Namespace Architecture combining a centralized i18n engine wit
|
|||||||
**Key Capabilities**:
|
**Key Capabilities**:
|
||||||
|
|
||||||
| Feature | Description |
|
| Feature | Description |
|
||||||
|---|---|
|
| -------------------- | ----------------------------------------------------------------------------------------- |
|
||||||
| 🌐 Hybrid Namespaces | Centralized `common` corpus + lazy-loaded feature dictionaries. |
|
| 🌐 Hybrid Namespaces | Centralized `common` corpus + lazy-loaded feature dictionaries. |
|
||||||
| 🛡️ Strict Typings | Native TS autocomplete for nested paths (e.g., `header.title`) via module augmentation. |
|
| 🛡️ Strict Typings | Native TS autocomplete for nested paths (e.g., `header.title`) via module augmentation. |
|
||||||
| 🔄 Safe Backend Sync | `changeLanguage` accepts a `syncCallback` with built-in rollback if the API fails. |
|
| 🔄 Safe Backend Sync | `changeLanguage` accepts a `syncCallback` with built-in rollback if the API fails. |
|
||||||
| 🏢 Tenant Overrides | `applyTenantOverrides` performs a partial deep-merge to selectively override terminology. |
|
| 🏢 Tenant Overrides | `applyTenantOverrides` performs a partial deep-merge to selectively override terminology. |
|
||||||
|
|
||||||
### 8. `packages/core-events`
|
### 8. `packages/core-events`
|
||||||
|
|
||||||
The **decoupled Nervous System** for the monorepo.
|
The **decoupled Nervous System** for the monorepo.
|
||||||
|
|
||||||
Provides a highly performant, strictly typed Event Bus (Pub/Sub) powered by [`mitt`](https://www.npmjs.com/package/mitt). It allows independent modules to communicate seamlessly without tightly coupling their codebases or triggering expensive global React tree re-renders.
|
Provides a highly performant, strictly typed Event Bus (Pub/Sub) powered by [`mitt`](https://www.npmjs.com/package/mitt). It allows independent modules to communicate seamlessly without tightly coupling their codebases or triggering expensive global React tree re-renders.
|
||||||
@@ -140,34 +149,40 @@ Provides a highly performant, strictly typed Event Bus (Pub/Sub) powered by [`mi
|
|||||||
**Key Capabilities**:
|
**Key Capabilities**:
|
||||||
|
|
||||||
| Feature | Description |
|
| Feature | Description |
|
||||||
|---|---|
|
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
||||||
| 🧩 Zero Coupling | Publishers and subscribers interact via blind events, eliminating direct module imports and circular dependencies. |
|
| 🧩 Zero Coupling | Publishers and subscribers interact via blind events, eliminating direct module imports and circular dependencies. |
|
||||||
| ⚡ Extreme Performance | Enables targeted DOM updates for high-frequency data streams (e.g., WebSockets) without re-rendering parent components. |
|
| ⚡ Extreme Performance | Enables targeted DOM updates for high-frequency data streams (e.g., WebSockets) without re-rendering parent components. |
|
||||||
| 🧹 Memory Safety | Native `useAppEvent` hook automatically unsubscribes on component unmount, preventing SPA memory leaks. |
|
| 🧹 Memory Safety | Native `useAppEvent` hook automatically unsubscribes on component unmount, preventing SPA memory leaks. |
|
||||||
| 🛡️ Strict Contracts | Centralized `events.registry.ts` enforces payload shapes via TypeScript, ensuring cross-module data safety. |
|
| 🛡️ Strict Contracts | Centralized `events.registry.ts` enforces payload shapes via TypeScript, ensuring cross-module data safety. |
|
||||||
|
|
||||||
### 9. `packages/utils`
|
### 9. `packages/utils`
|
||||||
|
|
||||||
Shared business logic and reusable utility modules that can be consumed across multiple applications. Fully tested using [Vitest](https://vitest.dev/).
|
Shared business logic and reusable utility modules that can be consumed across multiple applications. Fully tested using [Vitest](https://vitest.dev/).
|
||||||
|
|
||||||
This package is intended to hold non-UI, cross-cutting logic such as date/time handling, security helpers, and other common utilities. It is designed to be framework-agnostic, predictable, and easy to extend as the system evolves.
|
This package is intended to hold non-UI, cross-cutting logic such as date/time handling, security helpers, and other common utilities. It is designed to be framework-agnostic, predictable, and easy to extend as the system evolves.
|
||||||
|
|
||||||
### 10. `packages/ui`
|
### 10. `packages/ui`
|
||||||
|
|
||||||
Shared UI component library (Buttons, Inputs, Cards, Layouts) with a comprehensive **Form UI Library**.
|
Shared UI component library (Buttons, Inputs, Cards, Layouts) with a comprehensive **Form UI Library**.
|
||||||
|
|
||||||
* Ensures consistent design across all applications
|
- Ensures consistent design across all applications
|
||||||
* Designed to be consumed by both web apps and Storybook
|
- Designed to be consumed by both web apps and Storybook
|
||||||
* **Form UI Library**: 22 RHF-connected [Mantine](https://mantine.dev/) form components with [Zod](https://zod.dev/) validation and i18n error translation, built via a `withRHF()` HOC factory with `useController` micro-subscriptions and `React.memo` optimization for ERP-scale forms
|
- **Form UI Library**: 22 RHF-connected [Mantine](https://mantine.dev/) form components with [Zod](https://zod.dev/) validation and i18n error translation, built via a `withRHF()` HOC factory with `useController` micro-subscriptions and `React.memo` optimization for ERP-scale forms
|
||||||
|
|
||||||
### 11. `packages/configs`
|
### 11. `packages/configs`
|
||||||
|
|
||||||
Single source of truth for tooling configuration.
|
Single source of truth for tooling configuration.
|
||||||
* **eslint-config**: Shared [ESLint](https://eslint.org/) rules
|
|
||||||
* **typescript-config**: Shared `tsconfig.json` base configurations
|
- **eslint-config**: Shared [ESLint](https://eslint.org/) rules
|
||||||
|
- **typescript-config**: Shared `tsconfig.json` base configurations
|
||||||
|
|
||||||
## ⚙️ Configuration & Environment
|
## ⚙️ Configuration & Environment
|
||||||
|
|
||||||
### Turborepo Caching
|
### Turborepo Caching
|
||||||
|
|
||||||
This repository uses **[Turborepo](https://turbo.build/repo) caching** for builds, tests, and other artifacts.
|
This repository uses **[Turborepo](https://turbo.build/repo) caching** for builds, tests, and other artifacts.
|
||||||
To fully clean the workspace (dependencies, build outputs, and Turbo cache):
|
To fully clean the workspace (dependencies, build outputs, and Turbo cache):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
rm -rf node_modules **/*/node_modules .turbo **/*/.turbo dist **/*/dist out **/*/out web-dist **/*/web-dist release **/*/release
|
rm -rf node_modules **/*/node_modules .turbo **/*/.turbo dist **/*/dist out **/*/out web-dist **/*/web-dist release **/*/release
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ The platform-agnostic API engine for the monorepo. Provides an isolated HTTP cli
|
|||||||
---
|
---
|
||||||
|
|
||||||
## Architecture Overview
|
## Architecture Overview
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
graph TD
|
graph TD
|
||||||
%% ─── Styling Definitions (Dark-Mode Friendly Enterprise Palette) ───
|
%% ─── Styling Definitions (Dark-Mode Friendly Enterprise Palette) ───
|
||||||
@@ -157,8 +158,8 @@ export const apiClient = createHttpClient(
|
|||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
| Property | Type | Default | Description |
|
| Property | Type | Default | Description |
|
||||||
|---|---|---|---|
|
| ---------------- | ------------------------ | ------------- | ------------------------------------- |
|
||||||
| `baseURL` | `string` | *required* | Base URL for all requests |
|
| `baseURL` | `string` | _required_ | Base URL for all requests |
|
||||||
| `timeout` | `number` | `15000` | Default request timeout (ms) |
|
| `timeout` | `number` | `15000` | Default request timeout (ms) |
|
||||||
| `defaultHeaders` | `Record<string, string>` | `{}` | Headers applied to every request |
|
| `defaultHeaders` | `Record<string, string>` | `{}` | Headers applied to every request |
|
||||||
| `observability` | `IObservabilityAdapter` | `noopAdapter` | Observability adapter (Faro or no-op) |
|
| `observability` | `IObservabilityAdapter` | `noopAdapter` | Observability adapter (Faro or no-op) |
|
||||||
@@ -166,7 +167,7 @@ export const apiClient = createHttpClient(
|
|||||||
### Interceptor Hooks
|
### Interceptor Hooks
|
||||||
|
|
||||||
| Hook | Signature | Purpose |
|
| Hook | Signature | Purpose |
|
||||||
|---|---|---|
|
| ----------------- | ------------------------ | ------------------------------------------------ |
|
||||||
| `onRequest` | `(config) => config` | Inject auth tokens, tenant headers |
|
| `onRequest` | `(config) => config` | Inject auth tokens, tenant headers |
|
||||||
| `onResponse` | `(response) => response` | Transform response shapes |
|
| `onResponse` | `(response) => response` | Transform response shapes |
|
||||||
| `onResponseError` | `(error) => never` | App-specific error handling (e.g., 401 redirect) |
|
| `onResponseError` | `(error) => never` | App-specific error handling (e.g., 401 redirect) |
|
||||||
@@ -180,12 +181,11 @@ export const apiClient = createHttpClient(
|
|||||||
The observability layer operates in two complementary modes:
|
The observability layer operates in two complementary modes:
|
||||||
|
|
||||||
| Mode | Activation | What it does |
|
| Mode | Activation | What it does |
|
||||||
|---|---|---|
|
| ------------------------ | ------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
||||||
| **Baseline** (always on) | Automatic | Pushes structured logs to Faro/Loki on every request with `module.key`, `module.action`, HTTP method, and URL |
|
| **Baseline** (always on) | Automatic | Pushes structured logs to Faro/Loki on every request with `module.key`, `module.action`, HTTP method, and URL |
|
||||||
| **Custom Span** (opt-in) | Via `telemetryContext.customSpanName` | Creates an explicit OTel span with custom tags, visible in [Grafana Tempo](https://grafana.com/oss/tempo/) |
|
| **Custom Span** (opt-in) | Via `telemetryContext.customSpanName` | Creates an explicit OTel span with custom tags, visible in [Grafana Tempo](https://grafana.com/oss/tempo/) |
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE] > `trace.getActiveSpan()` returns `undefined` inside Axios interceptors due to browser XHR/Fetch lifecycle race conditions with Faro's `TracingInstrumentation`. The adapter does **not** attempt to enrich auto-instrumented spans. HTTP span capture is handled entirely by `TracingInstrumentation` auto-instrumentation.
|
||||||
> `trace.getActiveSpan()` returns `undefined` inside Axios interceptors due to browser XHR/Fetch lifecycle race conditions with Faro's `TracingInstrumentation`. The adapter does **not** attempt to enrich auto-instrumented spans. HTTP span capture is handled entirely by `TracingInstrumentation` auto-instrumentation.
|
|
||||||
|
|
||||||
### Initialization
|
### Initialization
|
||||||
|
|
||||||
@@ -207,7 +207,7 @@ initTelemetry({
|
|||||||
### `TelemetryConfig`
|
### `TelemetryConfig`
|
||||||
|
|
||||||
| Property | Type | Required | Description |
|
| Property | Type | Required | Description |
|
||||||
|---|---|---|---|
|
| ------------------------------ | ------------------------- | -------- | ------------------------------------------------------------------- |
|
||||||
| `appName` | `string` | ✅ | Application name for Faro + OTel resource attributes |
|
| `appName` | `string` | ✅ | Application name for Faro + OTel resource attributes |
|
||||||
| `appVersion` | `string` | ✅ | SemVer version |
|
| `appVersion` | `string` | ✅ | SemVer version |
|
||||||
| `telemetryUrl` | `string` | ✅ | Grafana Faro collector URL |
|
| `telemetryUrl` | `string` | ✅ | Grafana Faro collector URL |
|
||||||
@@ -220,7 +220,7 @@ initTelemetry({
|
|||||||
Every request dispatched through `BaseRemoteDataServices` automatically attaches two business audit headers:
|
Every request dispatched through `BaseRemoteDataServices` automatically attaches two business audit headers:
|
||||||
|
|
||||||
| Header | Source | Purpose |
|
| Header | Source | Purpose |
|
||||||
|---|---|---|
|
| ------------------ | ------------------------------ | ------------------------------------------------- |
|
||||||
| `ex-module-key` | `DataServicesConfig.moduleKey` | Identifies the business module (e.g., `BOOKING`) |
|
| `ex-module-key` | `DataServicesConfig.moduleKey` | Identifies the business module (e.g., `BOOKING`) |
|
||||||
| `ex-module-action` | `RequestDescriptor.action` | Identifies the operation (e.g., `READ`, `CREATE`) |
|
| `ex-module-action` | `RequestDescriptor.action` | Identifies the operation (e.g., `READ`, `CREATE`) |
|
||||||
|
|
||||||
@@ -229,7 +229,7 @@ These headers are extracted by the `faroAdapter` and included in all Faro `pushL
|
|||||||
### Span Safety Guarantees
|
### Span Safety Guarantees
|
||||||
|
|
||||||
| Guarantee | Mechanism |
|
| Guarantee | Mechanism |
|
||||||
|---|---|
|
| ---------------------------- | ----------------------------------------------------------------------------- |
|
||||||
| **No span leaks** | `safeEndSpan()` always closes the span and detaches the reference from config |
|
| **No span leaks** | `safeEndSpan()` always closes the span and detaches the reference from config |
|
||||||
| **No double-close on retry** | Span reference is deleted from config after `span.end()` |
|
| **No double-close on retry** | Span reference is deleted from config after `span.end()` |
|
||||||
| **No error swallowing** | All adapter calls are wrapped in try-catch in `create-http-client.ts` |
|
| **No error swallowing** | All adapter calls are wrapped in try-catch in `create-http-client.ts` |
|
||||||
@@ -254,19 +254,16 @@ interface BookingEntity extends BaseEntity {
|
|||||||
status: 'pending' | 'confirmed' | 'cancelled';
|
status: 'pending' | 'confirmed' | 'cancelled';
|
||||||
}
|
}
|
||||||
|
|
||||||
export const bookingServices = new CommonRemoteDataServices<BookingEntity>(
|
export const bookingServices = new CommonRemoteDataServices<BookingEntity>(apiClient, {
|
||||||
apiClient,
|
|
||||||
{
|
|
||||||
apiUrl: '/bookings',
|
apiUrl: '/bookings',
|
||||||
moduleKey: 'BOOKING',
|
moduleKey: 'BOOKING',
|
||||||
},
|
});
|
||||||
);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Available Operations
|
### Available Operations
|
||||||
|
|
||||||
| Method | HTTP | URL Template | Description |
|
| Method | HTTP | URL Template | Description |
|
||||||
|---|---|---|---|
|
| -------------------------------- | ------ | -------------------------------------------- | ----------------------- |
|
||||||
| `getMany(config?)` | GET | `/bookings` | Fetch paginated list |
|
| `getMany(config?)` | GET | `/bookings` | Fetch paginated list |
|
||||||
| `getOne(id, config?)` | GET | `/bookings/:id` | Fetch single entity |
|
| `getOne(id, config?)` | GET | `/bookings/:id` | Fetch single entity |
|
||||||
| `create(data, config?)` | POST | `/bookings` | Create new entity |
|
| `create(data, config?)` | POST | `/bookings` | Create new entity |
|
||||||
@@ -308,8 +305,11 @@ import { initTelemetry } from '@repo/core-api/observability/setup';
|
|||||||
initTelemetry({
|
initTelemetry({
|
||||||
appName: import.meta.env.VITE_APP_NAME || 'fe-monorepo-web',
|
appName: import.meta.env.VITE_APP_NAME || 'fe-monorepo-web',
|
||||||
appVersion: import.meta.env.VITE_APP_VERSION || '0.0.0',
|
appVersion: import.meta.env.VITE_APP_VERSION || '0.0.0',
|
||||||
telemetryUrl: import.meta.env.VITE_FARO_URL || '[https://telemetry.eigen.co.id/collect](https://telemetry.eigen.co.id/collect)',
|
telemetryUrl:
|
||||||
otlpTraceUrl: import.meta.env.VITE_OTLP_TRACE_URL || '[https://telemetry.eigen.co.id/v1/traces](https://telemetry.eigen.co.id/v1/traces)',
|
import.meta.env.VITE_FARO_URL || '[https://telemetry.eigen.co.id/collect](https://telemetry.eigen.co.id/collect)',
|
||||||
|
otlpTraceUrl:
|
||||||
|
import.meta.env.VITE_OTLP_TRACE_URL ||
|
||||||
|
'[https://telemetry.eigen.co.id/v1/traces](https://telemetry.eigen.co.id/v1/traces)',
|
||||||
environment: import.meta.env.VITE_ENV || 'development',
|
environment: import.meta.env.VITE_ENV || 'development',
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -345,10 +345,10 @@ export interface BookingEntity extends BaseEntity {
|
|||||||
totalAmount: number;
|
totalAmount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const bookingServices = new CommonRemoteDataServices<BookingEntity>(
|
export const bookingServices = new CommonRemoteDataServices<BookingEntity>(apiClient, {
|
||||||
apiClient,
|
apiUrl: '/bookings',
|
||||||
{ apiUrl: '/bookings', moduleKey: 'BOOKING' },
|
moduleKey: 'BOOKING',
|
||||||
);
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. Consume in a React Component
|
### 4. Consume in a React Component
|
||||||
@@ -426,7 +426,7 @@ await bookingServices.getMany({
|
|||||||
### What Happens at Each Stage
|
### What Happens at Each Stage
|
||||||
|
|
||||||
| Stage | Baseline (no telemetryContext) | With `customSpanName` |
|
| Stage | Baseline (no telemetryContext) | With `customSpanName` |
|
||||||
|---|---|---|
|
| ------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------- |
|
||||||
| **Request Start** | Faro `pushLog` (DEBUG) with `module.key`, `module.action`, URL | + Creates OTel span with `http.method`, `http.url`, `custom.*` tags |
|
| **Request Start** | Faro `pushLog` (DEBUG) with `module.key`, `module.action`, URL | + Creates OTel span with `http.method`, `http.url`, `custom.*` tags |
|
||||||
| **Request Success** | — | Closes span (OK). If `pushEventOnSuccess`, pushes Faro event |
|
| **Request Success** | — | Closes span (OK). If `pushEventOnSuccess`, pushes Faro event |
|
||||||
| **Request Error** | Faro `pushError` + `pushLog` (ERROR) | + Closes span (ERROR), records exception |
|
| **Request Error** | Faro `pushError` + `pushLog` (ERROR) | + Closes span (ERROR), records exception |
|
||||||
@@ -456,7 +456,7 @@ try {
|
|||||||
### Error Codes
|
### Error Codes
|
||||||
|
|
||||||
| Code | HTTP Status | Description |
|
| Code | HTTP Status | Description |
|
||||||
|---|---|---|
|
| --------------- | ----------- | -------------------------------------- |
|
||||||
| `BAD_REQUEST` | 400 | Invalid request parameters |
|
| `BAD_REQUEST` | 400 | Invalid request parameters |
|
||||||
| `UNAUTHORIZED` | 401 | Missing or expired token |
|
| `UNAUTHORIZED` | 401 | Missing or expired token |
|
||||||
| `FORBIDDEN` | 403 | Insufficient permissions |
|
| `FORBIDDEN` | 403 | Insufficient permissions |
|
||||||
@@ -471,7 +471,7 @@ try {
|
|||||||
## Package Exports
|
## Package Exports
|
||||||
|
|
||||||
| Import Path | Contents |
|
| Import Path | Contents |
|
||||||
|---|---|
|
| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------- |
|
||||||
| `@repo/core-api/http-client` | `createHttpClient`, `ApiResponse`, `TelemetryContext`, Axios type re-exports |
|
| `@repo/core-api/http-client` | `createHttpClient`, `ApiResponse`, `TelemetryContext`, Axios type re-exports |
|
||||||
| `@repo/core-api/observability` | `faroAdapter`, `noopObservabilityAdapter`, `IObservabilityAdapter`, `initTelemetry`, `getFaro`, `TelemetryConfig` |
|
| `@repo/core-api/observability` | `faroAdapter`, `noopObservabilityAdapter`, `IObservabilityAdapter`, `initTelemetry`, `getFaro`, `TelemetryConfig` |
|
||||||
| `@repo/core-api/observability/setup` | `initTelemetry`, `getFaro`, `TelemetryConfig` |
|
| `@repo/core-api/observability/setup` | `initTelemetry`, `getFaro`, `TelemetryConfig` |
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
In enterprise applications, the shape of data returned by the API (DTOs) often differs from the shape used in the frontend (Domain Entities). Common differences include:
|
In enterprise applications, the shape of data returned by the API (DTOs) often differs from the shape used in the frontend (Domain Entities). Common differences include:
|
||||||
|
|
||||||
| API (DTO) | Frontend (Entity) |
|
| API (DTO) | Frontend (Entity) |
|
||||||
| ------------------------------ | ---------------------------- |
|
| ------------------------ | --------------------------- |
|
||||||
| `snake_case` field names | `camelCase` field names |
|
| `snake_case` field names | `camelCase` field names |
|
||||||
| Deeply nested structures | Flattened/normalized shapes |
|
| Deeply nested structures | Flattened/normalized shapes |
|
||||||
| Raw ISO date strings | Parsed `Date` objects |
|
| Raw ISO date strings | Parsed `Date` objects |
|
||||||
@@ -54,6 +54,7 @@ graph LR
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Data flows:**
|
**Data flows:**
|
||||||
|
|
||||||
- **API → Frontend:** Response DTO → `transformToEntity()` → Domain Entity
|
- **API → Frontend:** Response DTO → `transformToEntity()` → Domain Entity
|
||||||
- **Frontend → API:** Domain Entity → `transformToDTO()` → Request DTO
|
- **Frontend → API:** Domain Entity → `transformToDTO()` → Request DTO
|
||||||
|
|
||||||
@@ -154,7 +155,7 @@ interface IDataTransformer<TEntity, TDTO> {
|
|||||||
Abstract class implementing `IDataTransformer` with sensible defaults.
|
Abstract class implementing `IDataTransformer` with sensible defaults.
|
||||||
|
|
||||||
| Method | Default Behavior | Override When |
|
| Method | Default Behavior | Override When |
|
||||||
| ------------------------- | ---------------------------------------- | ------------------------------------------ |
|
| -------------------------- | -------------------------------------- | ------------------------------------------------------- |
|
||||||
| `transformToEntity` | Identity cast (passthrough) | Always — this is the core mapping |
|
| `transformToEntity` | Identity cast (passthrough) | Always — this is the core mapping |
|
||||||
| `transformToDTO` | Identity cast (passthrough) | Always — this is the core mapping |
|
| `transformToDTO` | Identity cast (passthrough) | Always — this is the core mapping |
|
||||||
| `transformGetOneResponse` | Delegates to `transformToEntity` | `getOne` needs computed/derived fields |
|
| `transformGetOneResponse` | Delegates to `transformToEntity` | `getOne` needs computed/derived fields |
|
||||||
@@ -169,7 +170,7 @@ Abstract class implementing `IDataTransformer` with sensible defaults.
|
|||||||
When a transformer is injected via `DataServicesConfig.transformer`, the base service methods automatically apply transformations:
|
When a transformer is injected via `DataServicesConfig.transformer`, the base service methods automatically apply transformations:
|
||||||
|
|
||||||
| Service Method | Transformer Hook Used | Direction |
|
| Service Method | Transformer Hook Used | Direction |
|
||||||
| -------------- | -------------------------------- | --------------- |
|
| ----------------- | ----------------------------- | ----------------- |
|
||||||
| `getOne()` | `transformGetOneResponse()` | Response → Entity |
|
| `getOne()` | `transformGetOneResponse()` | Response → Entity |
|
||||||
| `getMany()` | `transformGetManyResponse()` | Response → Entity |
|
| `getMany()` | `transformGetManyResponse()` | Response → Entity |
|
||||||
| `create()` | `transformCreatePayload()` | Entity → DTO |
|
| `create()` | `transformCreatePayload()` | Entity → DTO |
|
||||||
@@ -277,8 +278,12 @@ Adding transformers to existing services requires **zero breaking changes**:
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class MyTransformer extends BaseDataTransformer<MyEntity, MyDTO> {
|
class MyTransformer extends BaseDataTransformer<MyEntity, MyDTO> {
|
||||||
transformToEntity(dto: MyDTO): MyEntity { /* ... */ }
|
transformToEntity(dto: MyDTO): MyEntity {
|
||||||
transformToDTO(entity: MyEntity): MyDTO { /* ... */ }
|
/* ... */
|
||||||
|
}
|
||||||
|
transformToDTO(entity: MyEntity): MyDTO {
|
||||||
|
/* ... */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -296,8 +301,12 @@ class MyTransformer extends BaseDataTransformer<MyEntity, MyDTO> {
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class MyTransformer extends BaseDataTransformer<MyEntity, MyDTO> {
|
class MyTransformer extends BaseDataTransformer<MyEntity, MyDTO> {
|
||||||
transformToEntity(dto: MyDTO): MyEntity { /* ... */ }
|
transformToEntity(dto: MyDTO): MyEntity {
|
||||||
transformToDTO(entity: MyEntity): MyDTO { /* ... */ }
|
/* ... */
|
||||||
|
}
|
||||||
|
transformToDTO(entity: MyEntity): MyDTO {
|
||||||
|
/* ... */
|
||||||
|
}
|
||||||
|
|
||||||
// Only override if getOne needs special handling
|
// Only override if getOne needs special handling
|
||||||
override transformGetOneResponse(dto: MyDTO): MyEntity {
|
override transformGetOneResponse(dto: MyDTO): MyEntity {
|
||||||
@@ -316,7 +325,7 @@ class MyTransformer extends BaseDataTransformer<MyEntity, MyDTO> {
|
|||||||
A full working example is available in the showcase booking feature:
|
A full working example is available in the showcase booking feature:
|
||||||
|
|
||||||
| File | Description |
|
| File | Description |
|
||||||
| ---- | ----------- |
|
| ------------------------------------------------------------------ | ------------------------------------------------------ |
|
||||||
| `apps/showcase/.../booking/data/booking.transformer.ts` | Basic transformer with snake_case ↔ camelCase mapping |
|
| `apps/showcase/.../booking/data/booking.transformer.ts` | Basic transformer with snake_case ↔ camelCase mapping |
|
||||||
| `apps/showcase/.../booking/data/booking.data-services.ts` | Data service with injected transformer |
|
| `apps/showcase/.../booking/data/booking.data-services.ts` | Data service with injected transformer |
|
||||||
| `apps/showcase/.../booking/data/advanced-booking.transformer.ts` | Extended transformer with custom chart method |
|
| `apps/showcase/.../booking/data/advanced-booking.transformer.ts` | Extended transformer with custom chart method |
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ The Global Pub/Sub & Hardware Integration Blueprint.
|
|||||||
### Architectural Topology
|
### Architectural Topology
|
||||||
|
|
||||||
### 1. Conceptual Topology: The Pub/Sub Data Flow
|
### 1. Conceptual Topology: The Pub/Sub Data Flow
|
||||||
|
|
||||||
This diagram illustrates the high-level concept of our decoupled architecture, demonstrating how application-specific types merge into the core bus.
|
This diagram illustrates the high-level concept of our decoupled architecture, demonstrating how application-specific types merge into the core bus.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
@@ -45,6 +46,7 @@ graph LR
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 2. System Architecture: Core Engine vs. App Autonomy
|
### 2. System Architecture: Core Engine vs. App Autonomy
|
||||||
|
|
||||||
This detailed diagram shows the exact boundaries between the @repo/core-events engine and the consuming application, highlighting real-world publishers (e.g., Cashier UI) and subscribers.
|
This detailed diagram shows the exact boundaries between the @repo/core-events engine and the consuming application, highlighting real-world publishers (e.g., Cashier UI) and subscribers.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
@@ -107,18 +109,16 @@ graph TD
|
|||||||
|
|
||||||
By routing communication through this centralized event bus, we achieve:
|
By routing communication through this centralized event bus, we achieve:
|
||||||
|
|
||||||
* **App Autonomy**: The core defines the engine. The app defines the contract. There is zero circular dependency.
|
- **App Autonomy**: The core defines the engine. The app defines the contract. There is zero circular dependency.
|
||||||
* **Zero Coupling**: Publishers and subscribers do not need to import, reference, or know about each other's existence.
|
- **Zero Coupling**: Publishers and subscribers do not need to import, reference, or know about each other's existence.
|
||||||
* **Extreme Performance**: Components can subscribe to high-frequency data streams (like WebSockets or hardware signals) and update their own local state *without* triggering massive React tree re-renders.
|
- **Extreme Performance**: Components can subscribe to high-frequency data streams (like WebSockets or hardware signals) and update their own local state _without_ triggering massive React tree re-renders.
|
||||||
* **Memory Safety**: The provided `useAppEvent` hook automatically handles subscription cleanup on component unmount, proactively preventing the most common source of memory leaks in Single Page Architectures (SPAs).
|
- **Memory Safety**: The provided `useAppEvent` hook automatically handles subscription cleanup on component unmount, proactively preventing the most common source of memory leaks in Single Page Architectures (SPAs).
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Defining Events (Module Augmentation)
|
## Defining Events (Module Augmentation)
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT] > **Do NOT add application events to `packages/core-events/src/events.registry.ts`.**
|
||||||
> **Do NOT add application events to `packages/core-events/src/events.registry.ts`.**
|
|
||||||
> The core registry is intentionally empty. Each app owns its own event contract.
|
> The core registry is intentionally empty. Each app owns its own event contract.
|
||||||
|
|
||||||
The core exports an open `AppEventRegistry` interface. Apps extend it using TypeScript's `declare module` syntax — the same pattern used for `@types/*` across the JS ecosystem.
|
The core exports an open `AppEventRegistry` interface. Apps extend it using TypeScript's `declare module` syntax — the same pattern used for `@types/*` across the JS ecosystem.
|
||||||
@@ -181,7 +181,7 @@ function OrderTracker() {
|
|||||||
### Why this pattern?
|
### Why this pattern?
|
||||||
|
|
||||||
| Concern | Old (Hardcoded) | New (Module Augmentation) |
|
| Concern | Old (Hardcoded) | New (Module Augmentation) |
|
||||||
|---|---|---|
|
| -------------------------------------- | --------------------- | ------------------------------------ |
|
||||||
| Core knows about app events? | ❌ Yes — violates IoC | ✅ No — core is a pure tool |
|
| Core knows about app events? | ❌ Yes — violates IoC | ✅ No — core is a pure tool |
|
||||||
| Adding events requires editing core? | ❌ Yes | ✅ No — edit your app's `.d.ts` only |
|
| Adding events requires editing core? | ❌ Yes | ✅ No — edit your app's `.d.ts` only |
|
||||||
| Multiple apps share the same registry? | ❌ Collision risk | ✅ Each app has its own `.d.ts` |
|
| Multiple apps share the same registry? | ❌ Collision risk | ✅ Each app has its own `.d.ts` |
|
||||||
@@ -223,6 +223,7 @@ Here are three real-world architectural patterns powered by the Event Bus. All e
|
|||||||
**Solution**: The UI publishes a blind event. A headless listener handles the platform routing.
|
**Solution**: The UI publishes a blind event. A headless listener handles the platform routing.
|
||||||
|
|
||||||
**Publisher (Cashier UI)**:
|
**Publisher (Cashier UI)**:
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { usePublishEvent } from '@repo/core-events';
|
import { usePublishEvent } from '@repo/core-events';
|
||||||
|
|
||||||
@@ -234,7 +235,7 @@ export function CashierUI() {
|
|||||||
publish('DEVICE:PRINT_RECEIPT', {
|
publish('DEVICE:PRINT_RECEIPT', {
|
||||||
receiptId: 'RCP-123',
|
receiptId: 'RCP-123',
|
||||||
items: [],
|
items: [],
|
||||||
total: 45.00,
|
total: 45.0,
|
||||||
cashierName: 'Firman',
|
cashierName: 'Firman',
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
});
|
});
|
||||||
@@ -245,6 +246,7 @@ export function CashierUI() {
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Subscriber (Headless Listener)**:
|
**Subscriber (Headless Listener)**:
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { useAppEvent } from '@repo/core-events';
|
import { useAppEvent } from '@repo/core-events';
|
||||||
|
|
||||||
@@ -274,6 +276,7 @@ export function PrinterListener() {
|
|||||||
**Solution**: The parent grid renders empty rows. Each row subscribes to the event bus and filters updates so it only re-renders when its specific data changes.
|
**Solution**: The parent grid renders empty rows. Each row subscribes to the event bus and filters updates so it only re-renders when its specific data changes.
|
||||||
|
|
||||||
**Parent Grid (Never re-renders)**:
|
**Parent Grid (Never re-renders)**:
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
export function LiveStockGrid() {
|
export function LiveStockGrid() {
|
||||||
// Generates 1000 IDs once. No stock data is stored here!
|
// Generates 1000 IDs once. No stock data is stored here!
|
||||||
@@ -292,6 +295,7 @@ export function LiveStockGrid() {
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Child Row (Targeted Updates)**:
|
**Child Row (Targeted Updates)**:
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { memo, useState } from 'react';
|
import { memo, useState } from 'react';
|
||||||
import { useAppEvent } from '@repo/core-events';
|
import { useAppEvent } from '@repo/core-events';
|
||||||
@@ -326,6 +330,7 @@ export const StockRow = memo(function StockRow({ stockId }) {
|
|||||||
**Solution**: The UI form announces the profile update. A dedicated storage listener persists it in the background, properly escalating errors if the storage fails.
|
**Solution**: The UI form announces the profile update. A dedicated storage listener persists it in the background, properly escalating errors if the storage fails.
|
||||||
|
|
||||||
**Publisher (Profile UI)**:
|
**Publisher (Profile UI)**:
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { usePublishEvent } from '@repo/core-events';
|
import { usePublishEvent } from '@repo/core-events';
|
||||||
|
|
||||||
@@ -347,6 +352,7 @@ export function ProfileSettingsUI() {
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Subscriber (Storage Sync Listener)**:
|
**Subscriber (Storage Sync Listener)**:
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { useAppEvent, usePublishEvent } from '@repo/core-events';
|
import { useAppEvent, usePublishEvent } from '@repo/core-events';
|
||||||
import { secureIndexedDB } from '@repo/core-storage';
|
import { secureIndexedDB } from '@repo/core-storage';
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
It provides a unified set of strictly-typed, secure, and fault-tolerant storage mechanisms tailored for React applications. It enforces strict **Inversion of Control (IoC)**—the core engine knows absolutely nothing about your application's business domains; instead, the consuming apps inject their own configurations and types.
|
It provides a unified set of strictly-typed, secure, and fault-tolerant storage mechanisms tailored for React applications. It enforces strict **Inversion of Control (IoC)**—the core engine knows absolutely nothing about your application's business domains; instead, the consuming apps inject their own configurations and types.
|
||||||
|
|
||||||
This package provides three primary storage solutions:
|
This package provides three primary storage solutions:
|
||||||
|
|
||||||
1. **Secure Local Storage** (Strict Key-Gatekeeping & AES encryption)
|
1. **Secure Local Storage** (Strict Key-Gatekeeping & AES encryption)
|
||||||
2. **Secure [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API)** (For larger key-value payloads)
|
2. **Secure [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API)** (For larger key-value payloads)
|
||||||
3. **Offline-First [PouchDB](https://pouchdb.com/)** (For document-oriented, bi-directional sync data)
|
3. **Offline-First [PouchDB](https://pouchdb.com/)** (For document-oriented, bi-directional sync data)
|
||||||
@@ -20,6 +21,7 @@ This package provides three primary storage solutions:
|
|||||||
Browser storage is notoriously vulnerable to XSS attacks and pollution. The `LocalStorageService` and `IndexedDBService` implement a strict **Gatekeeper** pattern to solve this.
|
Browser storage is notoriously vulnerable to XSS attacks and pollution. The `LocalStorageService` and `IndexedDBService` implement a strict **Gatekeeper** pattern to solve this.
|
||||||
|
|
||||||
By forcing developers to register every key explicitly into either `plainTextKeys` or `encryptedKeys`, the engine guarantees:
|
By forcing developers to register every key explicitly into either `plainTextKeys` or `encryptedKeys`, the engine guarantees:
|
||||||
|
|
||||||
1. No unapproved or rogue keys can ever be written or read (throws a `Security Exception`).
|
1. No unapproved or rogue keys can ever be written or read (throws a `Security Exception`).
|
||||||
2. Highly sensitive tokens (e.g., JWTs) are automatically routed through the `@repo/utils` AES Encryption pipeline before touching the disk.
|
2. Highly sensitive tokens (e.g., JWTs) are automatically routed through the `@repo/utils` AES Encryption pipeline before touching the disk.
|
||||||
|
|
||||||
@@ -107,10 +109,10 @@ const theme = await appStorage.getItem('THEME'); // Plaintext on disk
|
|||||||
|
|
||||||
### ✅ Do's and ❌ Don'ts
|
### ✅ Do's and ❌ Don'ts
|
||||||
|
|
||||||
* **✅ DO use TypeScript Literal Types** for your storage keys (`type Keys = 'A' | 'B'`) to get full IntelliSense.
|
- **✅ DO use TypeScript Literal Types** for your storage keys (`type Keys = 'A' | 'B'`) to get full IntelliSense.
|
||||||
* **✅ DO place Session/Auth tokens** exclusively inside the `encryptedKeys` Set.
|
- **✅ DO place Session/Auth tokens** exclusively inside the `encryptedKeys` Set.
|
||||||
* **❌ DON'T use native `window.localStorage` directly** anywhere in your React components. It bypasses our encryption and gatekeeper logic.
|
- **❌ DON'T use native `window.localStorage` directly** anywhere in your React components. It bypasses our encryption and gatekeeper logic.
|
||||||
* **❌ DON'T mix domain data.** Keep UI preferences (Theme, Sidebar state) in LocalStorage, and large datasets (Offline Caches) in IndexedDB.
|
- **❌ DON'T mix domain data.** Keep UI preferences (Theme, Sidebar state) in LocalStorage, and large datasets (Offline Caches) in IndexedDB.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -178,7 +180,7 @@ export const dbManager = new PouchDBManager();
|
|||||||
|
|
||||||
export const itemDB = dbManager.register<Item>({
|
export const itemDB = dbManager.register<Item>({
|
||||||
localName: 'items_db',
|
localName: 'items_db',
|
||||||
remoteUrl: 'http://admin:password@localhost:5984/items_db'
|
remoteUrl: 'http://admin:password@localhost:5984/items_db',
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -187,7 +189,7 @@ export const itemDB = dbManager.register<Item>({
|
|||||||
The registered database returns a `PouchService` instance. This wrapper fully abstracts the raw PouchDB API into clean, Promise-based helpers, auto-handling `_rev` conflicts.
|
The registered database returns a `PouchService` instance. This wrapper fully abstracts the raw PouchDB API into clean, Promise-based helpers, auto-handling `_rev` conflicts.
|
||||||
|
|
||||||
| Method | Description |
|
| Method | Description |
|
||||||
|---|---|
|
| ------------------ | --------------------------------------------------------------- |
|
||||||
| `create(data)` | Inserts a new document. Auto-generates `_id` if omitted. |
|
| `create(data)` | Inserts a new document. Auto-generates `_id` if omitted. |
|
||||||
| `update(id, data)` | Auto-fetches the latest `_rev` to merge payloads cleanly. |
|
| `update(id, data)` | Auto-fetches the latest `_rev` to merge payloads cleanly. |
|
||||||
| `delete(id)` | Auto-fetches the latest `_rev` to safely remove the document. |
|
| `delete(id)` | Auto-fetches the latest `_rev` to safely remove the document. |
|
||||||
@@ -197,13 +199,13 @@ The registered database returns a `PouchService` instance. This wrapper fully ab
|
|||||||
```typescript
|
```typescript
|
||||||
// Example: Querying data using selectors
|
// Example: Querying data using selectors
|
||||||
const expensiveItems = await itemDB.find({
|
const expensiveItems = await itemDB.find({
|
||||||
selector: { price: { $gt: 100 }, category: 'electronics' }
|
selector: { price: { $gt: 100 }, category: 'electronics' },
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Real-Time Reactivity (`onChange` Pub/Sub)
|
### 3. Real-Time Reactivity (`onChange` Pub/Sub)
|
||||||
|
|
||||||
We implemented a **Publisher-Subscriber (Pub/Sub)** pattern inside the wrapper to handle real-time data changes efficiently. The wrapper maintains a *single* background connection to the changes feed and broadcasts events to all React subscribers.
|
We implemented a **Publisher-Subscriber (Pub/Sub)** pattern inside the wrapper to handle real-time data changes efficiently. The wrapper maintains a _single_ background connection to the changes feed and broadcasts events to all React subscribers.
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { useEffect, useCallback, useState } from 'react';
|
import { useEffect, useCallback, useState } from 'react';
|
||||||
@@ -244,16 +246,22 @@ import type { ItemEntity, BookingEntity } from './types';
|
|||||||
export const envelopeDbManager = new PouchEnvelopeDBManager();
|
export const envelopeDbManager = new PouchEnvelopeDBManager();
|
||||||
|
|
||||||
// Registers to the SAME database 'master_db', but scoped to 'item'
|
// Registers to the SAME database 'master_db', but scoped to 'item'
|
||||||
export const itemDB = envelopeDbManager.register<ItemEntity>({
|
export const itemDB = envelopeDbManager.register<ItemEntity>(
|
||||||
|
{
|
||||||
localName: 'master_db',
|
localName: 'master_db',
|
||||||
remoteUrl: 'http://admin:pass@localhost:5984/master_db'
|
remoteUrl: 'http://admin:pass@localhost:5984/master_db',
|
||||||
}, 'item');
|
},
|
||||||
|
'item',
|
||||||
|
);
|
||||||
|
|
||||||
// Registers to the SAME database 'master_db', but scoped to 'booking'
|
// Registers to the SAME database 'master_db', but scoped to 'booking'
|
||||||
export const bookingDB = envelopeDbManager.register<BookingEntity>({
|
export const bookingDB = envelopeDbManager.register<BookingEntity>(
|
||||||
|
{
|
||||||
localName: 'master_db',
|
localName: 'master_db',
|
||||||
remoteUrl: 'http://admin:pass@localhost:5984/master_db'
|
remoteUrl: 'http://admin:pass@localhost:5984/master_db',
|
||||||
}, 'booking');
|
},
|
||||||
|
'booking',
|
||||||
|
);
|
||||||
|
|
||||||
// API usage remains identical!
|
// API usage remains identical!
|
||||||
await itemDB.create({ _id: '123', name: 'Widget' }); // Stored as "item:123"
|
await itemDB.create({ _id: '123', name: 'Widget' }); // Stored as "item:123"
|
||||||
@@ -265,16 +273,17 @@ const results = await itemDB.search('widget keyword', ['data.name', 'data.sku'])
|
|||||||
|
|
||||||
### ✅ Do's and ❌ Don'ts for PouchDB
|
### ✅ Do's and ❌ Don'ts for PouchDB
|
||||||
|
|
||||||
* **✅ DO use `.onChange()`** to make your UI reactive to background cloud syncs.
|
- **✅ DO use `.onChange()`** to make your UI reactive to background cloud syncs.
|
||||||
* **✅ DO return the `unsubscribe` function** in your `useEffect` cleanup block to prevent severe memory leaks.
|
- **✅ DO return the `unsubscribe` function** in your `useEffect` cleanup block to prevent severe memory leaks.
|
||||||
* **❌ DON'T use `db.raw.changes()`** inside your React components. It creates zombie WebSocket connections and tightly couples your UI to PouchDB's specific API.
|
- **❌ DON'T use `db.raw.changes()`** inside your React components. It creates zombie WebSocket connections and tightly couples your UI to PouchDB's specific API.
|
||||||
* **❌ DON'T pass the `_rev` property** manually when updating or deleting. The wrapper's `update()` and `delete()` methods handle revision fetching automatically.
|
- **❌ DON'T pass the `_rev` property** manually when updating or deleting. The wrapper's `update()` and `delete()` methods handle revision fetching automatically.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ⚠️ Troubleshooting
|
## ⚠️ Troubleshooting
|
||||||
|
|
||||||
### CouchDB CORS Infinite Retries
|
### CouchDB CORS Infinite Retries
|
||||||
|
|
||||||
By providing a `remoteUrl`, the engine runs bi-directional sync in the background (`live: true, retry: true`). Fault tolerance is guaranteed: if CouchDB crashes, local reads/writes continue uninterrupted.
|
By providing a `remoteUrl`, the engine runs bi-directional sync in the background (`live: true, retry: true`). Fault tolerance is guaranteed: if CouchDB crashes, local reads/writes continue uninterrupted.
|
||||||
|
|
||||||
However, if your browser blocks CouchDB sync with a **CORS error**, PouchDB will misinterpret this as a network failure and enter an infinite retry loop, flooding your Network tab.
|
However, if your browser blocks CouchDB sync with a **CORS error**, PouchDB will misinterpret this as a network failure and enter an infinite retry loop, flooding your Network tab.
|
||||||
|
|||||||
@@ -12,12 +12,7 @@ These components automatically adapt to screen sizes, handle tooltip generation,
|
|||||||
## Import Statement
|
## Import Statement
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import {
|
import { PageActions, RowActions, type PageAction, type RowAction } from '@repo/ui/components';
|
||||||
PageActions,
|
|
||||||
RowActions,
|
|
||||||
type PageAction,
|
|
||||||
type RowAction
|
|
||||||
} from '@repo/ui/components';
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage Examples
|
## Usage Examples
|
||||||
@@ -56,7 +51,7 @@ function PageHeader() {
|
|||||||
key: 'print-copy',
|
key: 'print-copy',
|
||||||
label: 'Print Copy',
|
label: 'Print Copy',
|
||||||
icon: <FileText size={16} />,
|
icon: <FileText size={16} />,
|
||||||
onClick: (k) => console.log(k)
|
onClick: (k) => console.log(k),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -133,14 +128,14 @@ function DataTable() {
|
|||||||
### PageActions Props
|
### PageActions Props
|
||||||
|
|
||||||
| Prop | Type | Default | Description |
|
| Prop | Type | Default | Description |
|
||||||
|---|---|---|---|
|
| --------- | -------------- | ----------- | ----------------------------------------------------------------- |
|
||||||
| `actions` | `PageAction[]` | Required | Array of configured page-level actions. |
|
| `actions` | `PageAction[]` | Required | Array of configured page-level actions. |
|
||||||
| `onClose` | `() => void` | `undefined` | Optional callback triggered when the close (X) button is clicked. |
|
| `onClose` | `() => void` | `undefined` | Optional callback triggered when the close (X) button is clicked. |
|
||||||
|
|
||||||
### RowActions Props
|
### RowActions Props
|
||||||
|
|
||||||
| Prop | Type | Default | Description |
|
| Prop | Type | Default | Description |
|
||||||
|---|---|---|---|
|
| ------------ | ------------- | ------- | ------------------------------------------------------------------------- |
|
||||||
| `actions` | `RowAction[]` | `[]` | Array of configured row-level actions. |
|
| `actions` | `RowAction[]` | `[]` | Array of configured row-level actions. |
|
||||||
| `showLabels` | `boolean` | `false` | If true, renders the text label alongside the icon for top-level buttons. |
|
| `showLabels` | `boolean` | `false` | If true, renders the text label alongside the icon for top-level buttons. |
|
||||||
|
|
||||||
@@ -151,7 +146,7 @@ Both `PageAction` and `RowAction` share a common base interface.
|
|||||||
**Base Action Properties (`BaseAction`)**
|
**Base Action Properties (`BaseAction`)**
|
||||||
|
|
||||||
| Property | Type | Description |
|
| Property | Type | Description |
|
||||||
|---|---|---|
|
| ---------- | --------------------------------------------------------------------------- | ----------------------------------------------------------------- |
|
||||||
| `key` | `string` | Unique identifier. Required for 'action', optional for 'divider'. |
|
| `key` | `string` | Unique identifier. Required for 'action', optional for 'divider'. |
|
||||||
| `type` | `'action'` \| `'divider'` | Type of action. Defaults to 'action'. |
|
| `type` | `'action'` \| `'divider'` | Type of action. Defaults to 'action'. |
|
||||||
| `icon` | `ReactNode` | Visual representation of the action. |
|
| `icon` | `ReactNode` | Visual representation of the action. |
|
||||||
@@ -162,7 +157,7 @@ Both `PageAction` and `RowAction` share a common base interface.
|
|||||||
**`PageAction` Specific Properties**
|
**`PageAction` Specific Properties**
|
||||||
|
|
||||||
| Property | Type | Description |
|
| Property | Type | Description |
|
||||||
|---|---|---|
|
| ---------- | -------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
|
||||||
| `label` | `string` | Text label displayed on the button. Required for 'action' type. |
|
| `label` | `string` | Text label displayed on the button. Required for 'action' type. |
|
||||||
| `variant` | `'filled'` \| `'light'` \| `'outline'` \| `'default'` \| `'subtle'` \| `'transparent'` | Specifies the Mantine button variant. Defaults to 'transparent' internally. |
|
| `variant` | `'filled'` \| `'light'` \| `'outline'` \| `'default'` \| `'subtle'` \| `'transparent'` | Specifies the Mantine button variant. Defaults to 'transparent' internally. |
|
||||||
| `children` | `PageAction[]` | Nested actions rendered as a dropdown menu below the main button. |
|
| `children` | `PageAction[]` | Nested actions rendered as a dropdown menu below the main button. |
|
||||||
@@ -170,7 +165,7 @@ Both `PageAction` and `RowAction` share a common base interface.
|
|||||||
**`RowAction` Specific Properties**
|
**`RowAction` Specific Properties**
|
||||||
|
|
||||||
| Property | Type | Description |
|
| Property | Type | Description |
|
||||||
|---|---|---|
|
| ---------- | ------------- | ------------------------------------------------------------ |
|
||||||
| `label` | `string` | Text primarily used when rendered inside a nested menu item. |
|
| `label` | `string` | Text primarily used when rendered inside a nested menu item. |
|
||||||
| `tooltip` | `string` | Optional text displayed on hover over the standalone icon. |
|
| `tooltip` | `string` | Optional text displayed on hover over the standalone icon. |
|
||||||
| `children` | `RowAction[]` | Nested actions that will be rendered inside a dropdown menu. |
|
| `children` | `RowAction[]` | Nested actions that will be rendered inside a dropdown menu. |
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ outline: [2, 3]
|
|||||||
>
|
>
|
||||||
> **Description:** Configuration-driven layout engine wrapping Mantine's AppShell, providing three layout variants (header-first, sidebar-first, top-nav), double sidebar support, responsive mobile drawers, and state persistence via Context API.
|
> **Description:** Configuration-driven layout engine wrapping Mantine's AppShell, providing three layout variants (header-first, sidebar-first, top-nav), double sidebar support, responsive mobile drawers, and state persistence via Context API.
|
||||||
|
|
||||||
> **Package**: `@repo/ui` · **Module Path**: `@repo/ui/components`
|
> **Package**: `@repo/ui` · **Module Path**: `@repo/ui/components` > **Dependencies**: React 18+, [Mantine v8](https://mantine.dev/) (`AppShell`), [`@mantine/hooks`](https://mantine.dev/hooks/package/)
|
||||||
> **Dependencies**: React 18+, [Mantine v8](https://mantine.dev/) (`AppShell`), [`@mantine/hooks`](https://mantine.dev/hooks/package/)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -101,7 +100,7 @@ interface CoreAppShellConfig {
|
|||||||
```
|
```
|
||||||
|
|
||||||
| Property | Type | Required | Description |
|
| Property | Type | Required | Description |
|
||||||
|---|---|---|---|
|
| ------------ | ------------------------ | -------- | -------------------------------------------- |
|
||||||
| `variant` | `LayoutVariant` | ✅ | Determines the structural layout mode |
|
| `variant` | `LayoutVariant` | ✅ | Determines the structural layout mode |
|
||||||
| `dimensions` | `CoreAppShellDimensions` | — | Override default pixel dimensions |
|
| `dimensions` | `CoreAppShellDimensions` | — | Override default pixel dimensions |
|
||||||
| `features` | `CoreAppShellFeatures` | — | Toggle optional layout regions and behaviors |
|
| `features` | `CoreAppShellFeatures` | — | Toggle optional layout regions and behaviors |
|
||||||
@@ -115,7 +114,7 @@ type LayoutVariant = 'header-first' | 'sidebar-first' | 'top-nav';
|
|||||||
```
|
```
|
||||||
|
|
||||||
| Variant | Mantine `layout` | Visual Description |
|
| Variant | Mantine `layout` | Visual Description |
|
||||||
|---|---|---|
|
| --------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `header-first` | `default` | Header spans the full viewport width. Sidebar and aside sit **below** the header, stretching to the bottom of the screen. Footer is inset between the sidebar and aside. This is the most common enterprise/dashboard pattern (e.g., Azure Portal, Jira). |
|
| `header-first` | `default` | Header spans the full viewport width. Sidebar and aside sit **below** the header, stretching to the bottom of the screen. Footer is inset between the sidebar and aside. This is the most common enterprise/dashboard pattern (e.g., Azure Portal, Jira). |
|
||||||
| `sidebar-first` | `alt` | Sidebar spans the full viewport height. Header sits **to the right** of the sidebar. Produces a "desktop application" feel (e.g., VS Code, Slack). Footer spans full width beneath the sidebar. |
|
| `sidebar-first` | `alt` | Sidebar spans the full viewport height. Header sits **to the right** of the sidebar. Produces a "desktop application" feel (e.g., VS Code, Slack). Footer spans full width beneath the sidebar. |
|
||||||
| `top-nav` | `default` | Header-only layout with **no visible desktop sidebar**. The sidebar is hidden on desktop but remains accessible as a mobile drawer on small screens. Ideal for documentation sites or marketing pages. |
|
| `top-nav` | `default` | Header-only layout with **no visible desktop sidebar**. The sidebar is hidden on desktop but remains accessible as a mobile drawer on small screens. Ideal for documentation sites or marketing pages. |
|
||||||
@@ -141,7 +140,7 @@ interface CoreAppShellFeatures {
|
|||||||
```
|
```
|
||||||
|
|
||||||
| Property | Type | Default | Description |
|
| Property | Type | Default | Description |
|
||||||
|---|---|---|---|
|
| ------------------------ | ------------------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `desktopCollapseVariant` | `'hide' \| 'mini'` | `'hide'` | **`hide`**: Sidebar slides out completely (collapsed width = 0). **`mini`**: Sidebar shrinks to `sidebarMiniWidth` showing only icons. |
|
| `desktopCollapseVariant` | `'hide' \| 'mini'` | `'hide'` | **`hide`**: Sidebar slides out completely (collapsed width = 0). **`mini`**: Sidebar shrinks to `sidebarMiniWidth` showing only icons. |
|
||||||
| `withUtilityBar` | `boolean` | Auto-detected | Show the utility bar above the header. If omitted, the bar renders when a `utilityBar` slot is provided. Set explicitly to `false` to suppress. |
|
| `withUtilityBar` | `boolean` | Auto-detected | Show the utility bar above the header. If omitted, the bar renders when a `utilityBar` slot is provided. Set explicitly to `false` to suppress. |
|
||||||
| `withAside` | `boolean` | Auto-detected | Show the right-hand aside panel. Same auto-detection logic as `withUtilityBar`. |
|
| `withAside` | `boolean` | Auto-detected | Show the right-hand aside panel. Same auto-detection logic as `withUtilityBar`. |
|
||||||
@@ -151,8 +150,7 @@ interface CoreAppShellFeatures {
|
|||||||
| `zIndex` | `number` | `200` | Base z-index passed to Mantine's `AppShell`. |
|
| `zIndex` | `number` | `200` | Base z-index passed to Mantine's `AppShell`. |
|
||||||
| `disabled` | `boolean` | `false` | Disables the AppShell layout entirely (renders children without structural chrome). |
|
| `disabled` | `boolean` | `false` | Disables the AppShell layout entirely (renders children without structural chrome). |
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP] > **Smart defaults**: You rarely need to set `withUtilityBar`, `withAside`, or `withFooter` explicitly. The engine auto-detects presence by checking if the corresponding slot is provided and truthy. Only set them to `false` when you want to **suppress** a slot that is being passed.
|
||||||
> **Smart defaults**: You rarely need to set `withUtilityBar`, `withAside`, or `withFooter` explicitly. The engine auto-detects presence by checking if the corresponding slot is provided and truthy. Only set them to `false` when you want to **suppress** a slot that is being passed.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -170,7 +168,7 @@ interface CoreAppShellDimensions {
|
|||||||
```
|
```
|
||||||
|
|
||||||
| Property | Type | Default | Description |
|
| Property | Type | Default | Description |
|
||||||
|---|---|---|---|
|
| ------------------ | ------------------ | ------- | ------------------------------------------------ |
|
||||||
| `utilityBarHeight` | `number \| string` | `32` | Height of the utility bar strip above the header |
|
| `utilityBarHeight` | `number \| string` | `32` | Height of the utility bar strip above the header |
|
||||||
| `headerHeight` | `number \| string` | `60` | Height of the main header |
|
| `headerHeight` | `number \| string` | `60` | Height of the main header |
|
||||||
| `sidebarWidth` | `number \| string` | `260` | Width of the expanded sidebar |
|
| `sidebarWidth` | `number \| string` | `260` | Width of the expanded sidebar |
|
||||||
@@ -201,7 +199,7 @@ interface CoreAppShellSlots {
|
|||||||
```
|
```
|
||||||
|
|
||||||
| Slot | Location | Notes |
|
| Slot | Location | Notes |
|
||||||
|---|---|---|
|
| --------------- | --------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `utilityBar` | Above the header, hidden on mobile (`display: none` below `sm`) | Typically used for environment banners, announcements, or top-level links. |
|
| `utilityBar` | Above the header, hidden on mobile (`display: none` below `sm`) | Typically used for environment banners, announcements, or top-level links. |
|
||||||
| `header` | Main application header | Must contain its own `<Burger>` for mobile toggle (use `useCoreAppShell()` context). |
|
| `header` | Main application header | Must contain its own `<Burger>` for mobile toggle (use `useCoreAppShell()` context). |
|
||||||
| `sidebar` | Desktop navbar body (single-sidebar mode) | Ignored when `withDoubleSidebar` is `true` — use `sidebarRail` + `sidebarPanel` instead. |
|
| `sidebar` | Desktop navbar body (single-sidebar mode) | Ignored when `withDoubleSidebar` is `true` — use `sidebarRail` + `sidebarPanel` instead. |
|
||||||
@@ -222,7 +220,7 @@ import { useCoreAppShell } from '@repo/ui/components';
|
|||||||
```
|
```
|
||||||
|
|
||||||
| Property / Method | Type | Description |
|
| Property / Method | Type | Description |
|
||||||
|---|---|---|
|
| --------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------ |
|
||||||
| `mobileOpened` | `boolean` | Whether the mobile drawer is currently open |
|
| `mobileOpened` | `boolean` | Whether the mobile drawer is currently open |
|
||||||
| `desktopOpened` | `boolean` | Whether the desktop sidebar is expanded (only applies when `desktopCollapseVariant` is `'hide'`) |
|
| `desktopOpened` | `boolean` | Whether the desktop sidebar is expanded (only applies when `desktopCollapseVariant` is `'hide'`) |
|
||||||
| `sidebarVariant` | `SidebarVariant` | Current sidebar mode: `'expanded'` \| `'mini'` \| `'hidden'` |
|
| `sidebarVariant` | `SidebarVariant` | Current sidebar mode: `'expanded'` \| `'mini'` \| `'hidden'` |
|
||||||
@@ -235,8 +233,7 @@ import { useCoreAppShell } from '@repo/ui/components';
|
|||||||
| `toggleNavbarPanel()` | `() => void` | Toggle the double-sidebar panel open/closed |
|
| `toggleNavbarPanel()` | `() => void` | Toggle the double-sidebar panel open/closed |
|
||||||
| `setSidebarVariant()` | `(variant: SidebarVariant) => void` | Programmatically set the sidebar to `'expanded'`, `'mini'`, or `'hidden'` |
|
| `setSidebarVariant()` | `(variant: SidebarVariant) => void` | Programmatically set the sidebar to `'expanded'`, `'mini'`, or `'hidden'` |
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING] > `useCoreAppShell()` **must** be called from within a `<CoreAppShell>` subtree. Calling it outside the provider will throw: `"useCoreAppShell must be used within CoreAppShellProvider"`. If you need context access in the header slot, pass a component (not inline JSX) so it mounts inside the provider tree.
|
||||||
> `useCoreAppShell()` **must** be called from within a `<CoreAppShell>` subtree. Calling it outside the provider will throw: `"useCoreAppShell must be used within CoreAppShellProvider"`. If you need context access in the header slot, pass a component (not inline JSX) so it mounts inside the provider tree.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -272,8 +269,12 @@ function App() {
|
|||||||
header: <MyHeader />,
|
header: <MyHeader />,
|
||||||
sidebar: (
|
sidebar: (
|
||||||
<Stack p="md" gap="xs">
|
<Stack p="md" gap="xs">
|
||||||
<Button variant="subtle" fullWidth>Dashboard</Button>
|
<Button variant="subtle" fullWidth>
|
||||||
<Button variant="subtle" fullWidth>Settings</Button>
|
Dashboard
|
||||||
|
</Button>
|
||||||
|
<Button variant="subtle" fullWidth>
|
||||||
|
Settings
|
||||||
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
@@ -300,7 +301,9 @@ function AppHeader() {
|
|||||||
<Group h="100%" px="md" justify="space-between">
|
<Group h="100%" px="md" justify="space-between">
|
||||||
<Group>
|
<Group>
|
||||||
<Burger opened={mobileOpened} onClick={toggleMobile} hiddenFrom="sm" size="sm" />
|
<Burger opened={mobileOpened} onClick={toggleMobile} hiddenFrom="sm" size="sm" />
|
||||||
<Text fw={700} size="lg">Enterprise Dashboard</Text>
|
<Text fw={700} size="lg">
|
||||||
|
Enterprise Dashboard
|
||||||
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
@@ -393,7 +396,9 @@ function App() {
|
|||||||
),
|
),
|
||||||
sidebarPanel: (
|
sidebarPanel: (
|
||||||
<Box p="md">
|
<Box p="md">
|
||||||
<Text fw={700} mb="sm">Navigation</Text>
|
<Text fw={700} mb="sm">
|
||||||
|
Navigation
|
||||||
|
</Text>
|
||||||
{/* Contextual links based on active rail icon */}
|
{/* Contextual links based on active rail icon */}
|
||||||
</Box>
|
</Box>
|
||||||
),
|
),
|
||||||
@@ -429,14 +434,17 @@ function ShellDemo() {
|
|||||||
const [collapseVariant, setCollapseVariant] = useState<DesktopCollapseVariant>('hide');
|
const [collapseVariant, setCollapseVariant] = useState<DesktopCollapseVariant>('hide');
|
||||||
const [withDoubleSidebar, setWithDoubleSidebar] = useState(false);
|
const [withDoubleSidebar, setWithDoubleSidebar] = useState(false);
|
||||||
|
|
||||||
const config: CoreAppShellConfig = useMemo(() => ({
|
const config: CoreAppShellConfig = useMemo(
|
||||||
|
() => ({
|
||||||
variant: layoutVariant,
|
variant: layoutVariant,
|
||||||
features: {
|
features: {
|
||||||
desktopCollapseVariant: collapseVariant,
|
desktopCollapseVariant: collapseVariant,
|
||||||
withDoubleSidebar,
|
withDoubleSidebar,
|
||||||
persistState: false,
|
persistState: false,
|
||||||
},
|
},
|
||||||
}), [layoutVariant, collapseVariant, withDoubleSidebar]);
|
}),
|
||||||
|
[layoutVariant, collapseVariant, withDoubleSidebar],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CoreAppShell config={config} slots={{ header: <MyHeader />, sidebar: <MySidebar /> }}>
|
<CoreAppShell config={config} slots={{ header: <MyHeader />, sidebar: <MySidebar /> }}>
|
||||||
@@ -467,7 +475,7 @@ interface CorePageContainerProps extends ContainerProps {
|
|||||||
```
|
```
|
||||||
|
|
||||||
| Prop | Type | Default | Description |
|
| Prop | Type | Default | Description |
|
||||||
|---|---|---|---|
|
| -------------- | ---------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `headerSlot` | `ReactNode` | — | Page-level header content (title, breadcrumbs, action buttons). Rendered above the main content with a bottom border. |
|
| `headerSlot` | `ReactNode` | — | Page-level header content (title, breadcrumbs, action buttons). Rendered above the main content with a bottom border. |
|
||||||
| `stickyHeader` | `boolean` | `false` | When `true`, the page header sticks to the top of the scroll area, offset by the AppShell header height via `var(--app-shell-header-offset)`. |
|
| `stickyHeader` | `boolean` | `false` | When `true`, the page header sticks to the top of the scroll area, offset by the AppShell header height via `var(--app-shell-header-offset)`. |
|
||||||
| `px` | `MantineSpacing` | `'md'` | Horizontal padding for both the header and content areas |
|
| `px` | `MantineSpacing` | `'md'` | Horizontal padding for both the header and content areas |
|
||||||
@@ -482,7 +490,9 @@ interface CorePageContainerProps extends ContainerProps {
|
|||||||
stickyHeader
|
stickyHeader
|
||||||
headerSlot={
|
headerSlot={
|
||||||
<Group justify="space-between">
|
<Group justify="space-between">
|
||||||
<Text component="h1" size="xl" fw={700}>Users</Text>
|
<Text component="h1" size="xl" fw={700}>
|
||||||
|
Users
|
||||||
|
</Text>
|
||||||
<Button>Add User</Button>
|
<Button>Add User</Button>
|
||||||
</Group>
|
</Group>
|
||||||
}
|
}
|
||||||
@@ -514,7 +524,7 @@ In `sidebar-first` mode, the footer spans the full viewport width (`left: 0; rig
|
|||||||
### Z-Index Strategy
|
### Z-Index Strategy
|
||||||
|
|
||||||
| Element | `header-first` | `sidebar-first` |
|
| Element | `header-first` | `sidebar-first` |
|
||||||
|---|---|---|
|
| --------------- | --------------- | --------------- |
|
||||||
| AppShell (base) | `200` (default) | `200` (default) |
|
| AppShell (base) | `200` (default) | `200` (default) |
|
||||||
| Navbar | `105` | `100` |
|
| Navbar | `105` | `100` |
|
||||||
| Aside | `105` | `100` |
|
| Aside | `105` | `100` |
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ outline: [2, 3]
|
|||||||
>
|
>
|
||||||
> **Description:** 22 pre-built form field components generated via a withRHF() HOC factory, integrating Mantine inputs with React Hook Form micro-subscriptions, Zod validation, and i18n error translation for ERP-scale performance.
|
> **Description:** 22 pre-built form field components generated via a withRHF() HOC factory, integrating Mantine inputs with React Hook Form micro-subscriptions, Zod validation, and i18n error translation for ERP-scale performance.
|
||||||
|
|
||||||
> **Package**: `@repo/ui` · **Module Path**: `@repo/ui/form`
|
> **Package**: `@repo/ui` · **Module Path**: `@repo/ui/form` > **Dependencies**: [React Hook Form](https://react-hook-form.com/) v7, [Zod](https://zod.dev/) v3, [Mantine](https://mantine.dev/) v8, `@repo/core-i18n`
|
||||||
> **Dependencies**: [React Hook Form](https://react-hook-form.com/) v7, [Zod](https://zod.dev/) v3, [Mantine](https://mantine.dev/) v8, `@repo/core-i18n`
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -69,7 +68,7 @@ withRHF<MantineComponentProps>(displayName, MantineComponent, options?)
|
|||||||
The factory accepts three arguments:
|
The factory accepts three arguments:
|
||||||
|
|
||||||
| Argument | Type | Description |
|
| Argument | Type | Description |
|
||||||
|---|---|---|
|
| ------------------ | ---------------- | ---------------------------------------------- |
|
||||||
| `displayName` | `string` | React DevTools name (e.g., `"FieldTextInput"`) |
|
| `displayName` | `string` | React DevTools name (e.g., `"FieldTextInput"`) |
|
||||||
| `MantineComponent` | `ComponentType` | The raw Mantine component |
|
| `MantineComponent` | `ComponentType` | The raw Mantine component |
|
||||||
| `options` | `WithRHFOptions` | Optional config for special components |
|
| `options` | `WithRHFOptions` | Optional config for special components |
|
||||||
@@ -77,7 +76,7 @@ The factory accepts three arguments:
|
|||||||
#### Options
|
#### Options
|
||||||
|
|
||||||
| Option | Default | Description |
|
| Option | Default | Description |
|
||||||
|---|---|---|
|
| ----------------- | ------- | ----------------------------------------------------------------------------------------- |
|
||||||
| `isCheckType` | `false` | Use `checked` instead of `value` (for Checkbox, Switch) |
|
| `isCheckType` | `false` | Use `checked` instead of `value` (for Checkbox, Switch) |
|
||||||
| `requiresWrapper` | `false` | Wrap in `Input.Wrapper` for error display (for ColorPicker, SegmentedControl, Chip.Group) |
|
| `requiresWrapper` | `false` | Wrap in `Input.Wrapper` for error display (for ColorPicker, SegmentedControl, Chip.Group) |
|
||||||
|
|
||||||
@@ -144,7 +143,6 @@ import { withRHF } from '../withRHF';
|
|||||||
export const FieldTextInput = withRHF<TextInputProps>('FieldTextInput', TextInput);
|
export const FieldTextInput = withRHF<TextInputProps>('FieldTextInput', TextInput);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Performance & Memoization
|
## Performance & Memoization
|
||||||
@@ -154,7 +152,7 @@ export const FieldTextInput = withRHF<TextInputProps>('FieldTextInput', TextInpu
|
|||||||
In enterprise ERP forms with **1500+ fields**, performance is critical:
|
In enterprise ERP forms with **1500+ fields**, performance is critical:
|
||||||
|
|
||||||
| Technique | What it prevents | Cost |
|
| Technique | What it prevents | Cost |
|
||||||
|---|---|---|
|
| ------------------- | -------------------------------------------------------------------------- | ----------------------------------------------- |
|
||||||
| **`useController`** | Global form state re-renders — each field subscribes only to its own slice | ~0 (hook-level isolation) |
|
| **`useController`** | Global form state re-renders — each field subscribes only to its own slice | ~0 (hook-level isolation) |
|
||||||
| **`React.memo`** | Parent-driven re-renders (e.g., grid layout changes, tab switches) | O(n) shallow prop comparison (typically n < 10) |
|
| **`React.memo`** | Parent-driven re-renders (e.g., grid layout changes, tab switches) | O(n) shallow prop comparison (typically n < 10) |
|
||||||
|
|
||||||
@@ -185,10 +183,13 @@ Encode Zod errors as JSON with a translation key:
|
|||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
name: z.string().min(3, JSON.stringify({
|
name: z.string().min(
|
||||||
|
3,
|
||||||
|
JSON.stringify({
|
||||||
key: 'validation:min_length',
|
key: 'validation:min_length',
|
||||||
values: { min: 3 },
|
values: { min: 3 },
|
||||||
})),
|
}),
|
||||||
|
),
|
||||||
});
|
});
|
||||||
// Error displayed: t('validation:min_length', { min: 3 })
|
// Error displayed: t('validation:min_length', { min: 3 })
|
||||||
// → "Minimum 3 characters" (from validation namespace)
|
// → "Minimum 3 characters" (from validation namespace)
|
||||||
@@ -316,7 +317,7 @@ The `registry.validator.ts` provides a set of pre-configured atomic validators r
|
|||||||
### Available Atomic Validators
|
### Available Atomic Validators
|
||||||
|
|
||||||
| Category | Validator | Target Type | Description |
|
| Category | Validator | Target Type | Description |
|
||||||
|---|---|---|---|
|
| ------------- | ------------------------------- | ----------- | ----------------------------------------------------------------------- |
|
||||||
| **Numeric** | `minValue(min, field?)` | `ZodNumber` | Minimum numeric value |
|
| **Numeric** | `minValue(min, field?)` | `ZodNumber` | Minimum numeric value |
|
||||||
| **Numeric** | `maxValue(max, field?)` | `ZodNumber` | Maximum numeric value |
|
| **Numeric** | `maxValue(max, field?)` | `ZodNumber` | Maximum numeric value |
|
||||||
| **Numeric** | `rangeValue(min, max, field?)` | `ZodNumber` | Restricts value between `min` and `max` limits |
|
| **Numeric** | `rangeValue(min, max, field?)` | `ZodNumber` | Restricts value between `min` and `max` limits |
|
||||||
@@ -343,11 +344,7 @@ import { z } from 'zod';
|
|||||||
import { compose, required, minLength, complexPassword } from '@repo/ui/validators';
|
import { compose, required, minLength, complexPassword } from '@repo/ui/validators';
|
||||||
|
|
||||||
export const userRegistrationSchema = z.object({
|
export const userRegistrationSchema = z.object({
|
||||||
password: compose(
|
password: compose(z.string(), required('Password'), complexPassword(8)),
|
||||||
z.string(),
|
|
||||||
required('Password'),
|
|
||||||
complexPassword(8)
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -364,7 +361,7 @@ it('minValue() should enforce min', () => {
|
|||||||
|
|
||||||
expect(res.success).toBe(false);
|
expect(res.success).toBe(false);
|
||||||
expect(res.error?.issues[0].message).toBe(
|
expect(res.error?.issues[0].message).toBe(
|
||||||
JSON.stringify({ key: 'validation:min_val', values: { min: 10, field: 'Age' } })
|
JSON.stringify({ key: 'validation:min_val', values: { min: 10, field: 'Age' } }),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
@@ -383,7 +380,7 @@ To decouple complex rendering side-effects from your component's root render fun
|
|||||||
The hook supports two cleanup strategies defined by the `mode` parameter:
|
The hook supports two cleanup strategies defined by the `mode` parameter:
|
||||||
|
|
||||||
| Mode | Behavior | Use Case |
|
| Mode | Behavior | Use Case |
|
||||||
|---|---|---|
|
| ------------ | ------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
||||||
| `unregister` | Completely unmounts the field. Value is wiped. Key is removed from submission payload. | Hidden fields (e.g. Spouse Name if "Single" is checked). |
|
| `unregister` | Completely unmounts the field. Value is wiped. Key is removed from submission payload. | Hidden fields (e.g. Spouse Name if "Single" is checked). |
|
||||||
| `reset` | Field stays active/disabled. Value is wiped. Error state is cleared. Key is sent in payload as empty/default. | Disabled or Cascading fields (e.g. Email Input if "Subscribe" is false, or resetting City when Province changes). |
|
| `reset` | Field stays active/disabled. Value is wiped. Error state is cleared. Key is sent in payload as empty/default. | Disabled or Cascading fields (e.g. Email Input if "Subscribe" is false, or resetting City when Province changes). |
|
||||||
|
|
||||||
@@ -405,7 +402,7 @@ export function ExampleForm() {
|
|||||||
name: 'corporateTaxId',
|
name: 'corporateTaxId',
|
||||||
setValue,
|
setValue,
|
||||||
unregister,
|
unregister,
|
||||||
mode: 'unregister'
|
mode: 'unregister',
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2. Reset Mode (Visible but Disabled)
|
// 2. Reset Mode (Visible but Disabled)
|
||||||
@@ -414,7 +411,7 @@ export function ExampleForm() {
|
|||||||
name: 'newsletterEmail',
|
name: 'newsletterEmail',
|
||||||
setValue,
|
setValue,
|
||||||
clearErrors,
|
clearErrors,
|
||||||
mode: 'reset'
|
mode: 'reset',
|
||||||
});
|
});
|
||||||
|
|
||||||
return <form>...</form>;
|
return <form>...</form>;
|
||||||
@@ -427,8 +424,7 @@ When dealing with cascading dependencies (e.g., Department -> Role), changing th
|
|||||||
|
|
||||||
You can accomplish this easily by supplying `mode: 'reset'` to `useConditionalField`. However, there is a **critical rendering caveat** with Mantine's `Select` (and similar complex visual inputs):
|
You can accomplish this easily by supplying `mode: 'reset'` to `useConditionalField`. However, there is a **critical rendering caveat** with Mantine's `Select` (and similar complex visual inputs):
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING] > **The Dynamic Key Trick:** Mantine components aggressively cache their internal visual text state. Even if `useConditionalField` perfectly resets the React Hook Form payload state to `''`, Mantine may still visually display the old, stale text on the screen.
|
||||||
> **The Dynamic Key Trick:** Mantine components aggressively cache their internal visual text state. Even if `useConditionalField` perfectly resets the React Hook Form payload state to `''`, Mantine may still visually display the old, stale text on the screen.
|
|
||||||
>
|
>
|
||||||
> To fix this UI desync, you **must bind the parent dependency to the child component's `key` prop**. This forces React's reconciliation engine to completely unmount and remount the child DOM node, flushing Mantine's internal cache and guaranteeing perfect UI synchronization.
|
> To fix this UI desync, you **must bind the parent dependency to the child component's `key` prop**. This forces React's reconciliation engine to completely unmount and remount the child DOM node, flushing Mantine's internal cache and guaranteeing perfect UI synchronization.
|
||||||
|
|
||||||
@@ -446,12 +442,16 @@ export function DepartmentForm() {
|
|||||||
const role = useWatch({ control, name: 'role' });
|
const role = useWatch({ control, name: 'role' });
|
||||||
|
|
||||||
// Derive available options based on the parent state
|
// Derive available options based on the parent state
|
||||||
const currentRoleOptions = department === 'IT'
|
const currentRoleOptions =
|
||||||
? [{ value: 'FRONTEND', label: 'Frontend' }, { value: 'BACKEND', label: 'Backend' }]
|
department === 'IT'
|
||||||
|
? [
|
||||||
|
{ value: 'FRONTEND', label: 'Frontend' },
|
||||||
|
{ value: 'BACKEND', label: 'Backend' },
|
||||||
|
]
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
// Determine if the currently selected role is still mathematically valid
|
// Determine if the currently selected role is still mathematically valid
|
||||||
const isRoleValid = !role || (!!department && currentRoleOptions.some(opt => opt.value === role));
|
const isRoleValid = !role || (!!department && currentRoleOptions.some((opt) => opt.value === role));
|
||||||
|
|
||||||
// 3. Reset Mode: Automatically wipes the field value in the RHF Payload if it becomes invalid
|
// 3. Reset Mode: Automatically wipes the field value in the RHF Payload if it becomes invalid
|
||||||
useConditionalField({
|
useConditionalField({
|
||||||
@@ -460,7 +460,7 @@ export function DepartmentForm() {
|
|||||||
setValue,
|
setValue,
|
||||||
clearErrors,
|
clearErrors,
|
||||||
mode: 'reset',
|
mode: 'reset',
|
||||||
defaultValue: ''
|
defaultValue: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -493,6 +493,7 @@ export function DepartmentForm() {
|
|||||||
Mantine's native `Select` and `MultiSelect` are string-based: they store `string | null` and `string[]` respectively. In enterprise applications, we often need to store **full objects** (`T | null` or `T[]`) in RHF state — for example, a user object `{ id: '1', name: 'Alice', email: 'alice@co.com' }` rather than just `'1'`.
|
Mantine's native `Select` and `MultiSelect` are string-based: they store `string | null` and `string[]` respectively. In enterprise applications, we often need to store **full objects** (`T | null` or `T[]`) in RHF state — for example, a user object `{ id: '1', name: 'Alice', email: 'alice@co.com' }` rather than just `'1'`.
|
||||||
|
|
||||||
The **LocalSelect** and **AsyncSelect** engines bridge this gap by:
|
The **LocalSelect** and **AsyncSelect** engines bridge this gap by:
|
||||||
|
|
||||||
1. Mapping `T[]` → `ComboboxItem[]` for Mantine rendering (via `valueKey` + `labelKey`/`renderLabel`)
|
1. Mapping `T[]` → `ComboboxItem[]` for Mantine rendering (via `valueKey` + `labelKey`/`renderLabel`)
|
||||||
2. Building an O(1) reverse lookup map (`Map<string, T>`) for resolving string changes back to full objects
|
2. Building an O(1) reverse lookup map (`Map<string, T>`) for resolving string changes back to full objects
|
||||||
3. Intercepting `onChange` to pass resolved objects to RHF
|
3. Intercepting `onChange` to pass resolved objects to RHF
|
||||||
@@ -503,7 +504,7 @@ The **LocalSelect** and **AsyncSelect** engines bridge this gap by:
|
|||||||
### Single vs. Multi-Select Data Mapping
|
### Single vs. Multi-Select Data Mapping
|
||||||
|
|
||||||
| Mode | Mantine Component | RHF Value | Mantine `value` Prop | `onChange` Payload |
|
| Mode | Mantine Component | RHF Value | Mantine `value` Prop | `onChange` Payload |
|
||||||
|---|---|---|---|---|
|
| ---------------------------- | ----------------- | ----------- | -------------------- | ------------------ |
|
||||||
| `multiple={false}` (default) | `<Select />` | `T \| null` | `string \| null` | `T \| null` |
|
| `multiple={false}` (default) | `<Select />` | `T \| null` | `string \| null` | `T \| null` |
|
||||||
| `multiple={true}` | `<MultiSelect />` | `T[]` | `string[]` | `T[]` |
|
| `multiple={true}` | `<MultiSelect />` | `T[]` | `string[]` | `T[]` |
|
||||||
|
|
||||||
@@ -514,7 +515,7 @@ Accepts a static `data` array of objects. No async fetching.
|
|||||||
#### Props
|
#### Props
|
||||||
|
|
||||||
| Prop | Type | Required | Description |
|
| Prop | Type | Required | Description |
|
||||||
|---|---|---|---|
|
| ----------------------------------------- | ----------------------------------- | -------- | -------------------------------------------- |
|
||||||
| `options` | `T[]` | ✅ | Array of objects to select from |
|
| `options` | `T[]` | ✅ | Array of objects to select from |
|
||||||
| `valueKey` | `keyof T & string` | ✅ | Property used as the unique identifier |
|
| `valueKey` | `keyof T & string` | ✅ | Property used as the unique identifier |
|
||||||
| `labelKey` | `keyof T & string` | — | Property used as the display label |
|
| `labelKey` | `keyof T & string` | — | Property used as the display label |
|
||||||
@@ -574,7 +575,7 @@ Uses **Inversion of Control**: the component does NOT handle API calls directly.
|
|||||||
#### Props
|
#### Props
|
||||||
|
|
||||||
| Prop | Type | Required | Description |
|
| Prop | Type | Required | Description |
|
||||||
|---|---|---|---|
|
| ----------------------------------------- | --------------------- | -------- | --------------------------------------------------------------------------------------------- |
|
||||||
| `loadOptions` | `LoadOptionsFn<T>` | ✅ | Async callback: `(search, page, prevOptions) => Promise<{ options: T[], hasMore?: boolean }>` |
|
| `loadOptions` | `LoadOptionsFn<T>` | ✅ | Async callback: `(search, page, prevOptions) => Promise<{ options: T[], hasMore?: boolean }>` |
|
||||||
| `defaultOptions` | `T[]` | — | Pre-loaded objects always present in dropdown (for edit forms) |
|
| `defaultOptions` | `T[]` | — | Pre-loaded objects always present in dropdown (for edit forms) |
|
||||||
| `debounceMs` | `number` | — | Search debounce delay (default: 300) |
|
| `debounceMs` | `number` | — | Search debounce delay (default: 300) |
|
||||||
@@ -733,10 +734,12 @@ For complex dynamic forms, developers often default to `.superRefine` or `.refin
|
|||||||
#### ❌ Bad: Manual Parsing (O(n) CPU Spike)
|
#### ❌ Bad: Manual Parsing (O(n) CPU Spike)
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
const badSchema = z.object({
|
const badSchema = z
|
||||||
|
.object({
|
||||||
userType: z.enum(['PERSONAL', 'CORPORATE']),
|
userType: z.enum(['PERSONAL', 'CORPORATE']),
|
||||||
corporateTaxId: z.string().optional()
|
corporateTaxId: z.string().optional(),
|
||||||
}).superRefine((data, ctx) => {
|
})
|
||||||
|
.superRefine((data, ctx) => {
|
||||||
if (data.userType === 'CORPORATE') {
|
if (data.userType === 'CORPORATE') {
|
||||||
// ⚠️ INCREDIBLY SLOW: Manual parsing inside refine loop
|
// ⚠️ INCREDIBLY SLOW: Manual parsing inside refine loop
|
||||||
const res = taxIdValidator.safeParse(data.corporateTaxId);
|
const res = taxIdValidator.safeParse(data.corporateTaxId);
|
||||||
@@ -748,14 +751,16 @@ const badSchema = z.object({
|
|||||||
#### ✅ Good: Declarative Unions (O(1) Evaluation)
|
#### ✅ Good: Declarative Unions (O(1) Evaluation)
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
const goodSchema = z.object({
|
const goodSchema = z
|
||||||
|
.object({
|
||||||
userType: z.enum(['PERSONAL', 'CORPORATE']),
|
userType: z.enum(['PERSONAL', 'CORPORATE']),
|
||||||
corporateTaxId: z.string().optional()
|
corporateTaxId: z.string().optional(),
|
||||||
}).and(
|
})
|
||||||
|
.and(
|
||||||
z.discriminatedUnion('userType', [
|
z.discriminatedUnion('userType', [
|
||||||
z.object({ userType: z.literal('PERSONAL') }),
|
z.object({ userType: z.literal('PERSONAL') }),
|
||||||
z.object({ userType: z.literal('CORPORATE'), corporateTaxId: taxIdValidator })
|
z.object({ userType: z.literal('CORPORATE'), corporateTaxId: taxIdValidator }),
|
||||||
])
|
]),
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -798,25 +803,20 @@ function LoginForm() {
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { useForm, type SubmitHandler } from 'react-hook-form';
|
import { useForm, type SubmitHandler } from 'react-hook-form';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import {
|
import { FieldTextInput, FieldNumberInput, FieldSelect, FieldCheckbox } from '@repo/ui/form';
|
||||||
FieldTextInput,
|
|
||||||
FieldNumberInput,
|
|
||||||
FieldSelect,
|
|
||||||
FieldCheckbox,
|
|
||||||
} from '@repo/ui/form';
|
|
||||||
|
|
||||||
const productSchema = z.object({
|
const productSchema = z.object({
|
||||||
name: z.string().min(1, {
|
name: z.string().min(1, {
|
||||||
message: JSON.stringify({ key: 'validation:required', values: { field: 'Product Name' } })
|
message: JSON.stringify({ key: 'validation:required', values: { field: 'Product Name' } }),
|
||||||
}),
|
}),
|
||||||
sku: z.string().regex(/^[A-Z]{3}-\d{4}$/, {
|
sku: z.string().regex(/^[A-Z]{3}-\d{4}$/, {
|
||||||
message: JSON.stringify({ key: 'validation:invalid_format', values: { format: 'AAA-0000' } })
|
message: JSON.stringify({ key: 'validation:invalid_format', values: { format: 'AAA-0000' } }),
|
||||||
}),
|
}),
|
||||||
price: z.number().min(0, {
|
price: z.number().min(0, {
|
||||||
message: JSON.stringify({ key: 'validation:min_value', values: { min: 0 } })
|
message: JSON.stringify({ key: 'validation:min_value', values: { min: 0 } }),
|
||||||
}),
|
}),
|
||||||
category: z.string().min(1, {
|
category: z.string().min(1, {
|
||||||
message: JSON.stringify({ key: 'validation:required', values: { field: 'Category' } })
|
message: JSON.stringify({ key: 'validation:required', values: { field: 'Category' } }),
|
||||||
}),
|
}),
|
||||||
isActive: z.boolean(),
|
isActive: z.boolean(),
|
||||||
});
|
});
|
||||||
@@ -842,12 +842,7 @@ function ProductEditor() {
|
|||||||
<FieldTextInput name="name" control={control} label="Product Name" />
|
<FieldTextInput name="name" control={control} label="Product Name" />
|
||||||
<FieldTextInput name="sku" control={control} label="SKU" placeholder="ABC-1234" />
|
<FieldTextInput name="sku" control={control} label="SKU" placeholder="ABC-1234" />
|
||||||
<FieldNumberInput name="price" control={control} label="Price" min={0} prefix="$" />
|
<FieldNumberInput name="price" control={control} label="Price" min={0} prefix="$" />
|
||||||
<FieldSelect
|
<FieldSelect name="category" control={control} label="Category" data={['Electronics', 'Clothing', 'Food']} />
|
||||||
name="category"
|
|
||||||
control={control}
|
|
||||||
label="Category"
|
|
||||||
data={['Electronics', 'Clothing', 'Food']}
|
|
||||||
/>
|
|
||||||
<FieldCheckbox name="isActive" control={control} label="Active" />
|
<FieldCheckbox name="isActive" control={control} label="Active" />
|
||||||
<button type="submit">Save Product</button>
|
<button type="submit">Save Product</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -863,10 +858,7 @@ Use `withRHF` directly to wrap any Mantine component not included in the library
|
|||||||
import { DatePickerInput, type DatePickerInputProps } from '@mantine/dates';
|
import { DatePickerInput, type DatePickerInputProps } from '@mantine/dates';
|
||||||
import { withRHF } from '@repo/ui/form';
|
import { withRHF } from '@repo/ui/form';
|
||||||
|
|
||||||
export const FieldDatePicker = withRHF<DatePickerInputProps>(
|
export const FieldDatePicker = withRHF<DatePickerInputProps>('FieldDatePicker', DatePickerInput);
|
||||||
'FieldDatePicker',
|
|
||||||
DatePickerInput,
|
|
||||||
);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -874,7 +866,7 @@ export const FieldDatePicker = withRHF<DatePickerInputProps>(
|
|||||||
## Component Reference
|
## Component Reference
|
||||||
|
|
||||||
| Component | Mantine Source | Type | Notes |
|
| Component | Mantine Source | Type | Notes |
|
||||||
|---|---|---|---|
|
| ----------------------- | ---------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- |
|
||||||
| `FieldTextInput` | `TextInput` | Text | Standard text input |
|
| `FieldTextInput` | `TextInput` | Text | Standard text input |
|
||||||
| `FieldPasswordInput` | `PasswordInput` | Text | Password with visibility toggle |
|
| `FieldPasswordInput` | `PasswordInput` | Text | Password with visibility toggle |
|
||||||
| `FieldTextarea` | `Textarea` | Text | Multi-line text |
|
| `FieldTextarea` | `Textarea` | Text | Multi-line text |
|
||||||
@@ -902,6 +894,7 @@ export const FieldDatePicker = withRHF<DatePickerInputProps>(
|
|||||||
| `FieldRichTextEditor` | `@mantine/tiptap` | `string` (HTML) |
|
| `FieldRichTextEditor` | `@mantine/tiptap` | `string` (HTML) |
|
||||||
|
|
||||||
### Rich Text Editor (TipTap)
|
### Rich Text Editor (TipTap)
|
||||||
|
|
||||||
The `FieldRichTextEditor` component integrates [`@mantine/tiptap`](https://mantine.dev/x/tiptap/) directly with React Hook Form. It safely stores the Editor's HTML output directly into the RHF state as a `string`. Because TipTap is an uncontrolled editor natively, this field uses a specialized `useController` wrapper that automatically syncs bidirectional updates (e.g., calling `editor.commands.setContent(field.value)` when the form is reset or async default values arrive).
|
The `FieldRichTextEditor` component integrates [`@mantine/tiptap`](https://mantine.dev/x/tiptap/) directly with React Hook Form. It safely stores the Editor's HTML output directly into the RHF state as a `string`. Because TipTap is an uncontrolled editor natively, this field uses a specialized `useController` wrapper that automatically syncs bidirectional updates (e.g., calling `editor.commands.setContent(field.value)` when the form is reset or async default values arrive).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ The centralized UI component library for the monorepo. Provides consistent desig
|
|||||||
## Exports
|
## Exports
|
||||||
|
|
||||||
| Entry Point | Path | Description |
|
| Entry Point | Path | Description |
|
||||||
|---|---|---|
|
| --------------------- | -------------------------------- | ---------------------------------------------------------------- |
|
||||||
| `@repo/ui/components` | `./src/components/index.ts` | All components (Mantine re-exports + system pages + Form fields) |
|
| `@repo/ui/components` | `./src/components/index.ts` | All components (Mantine re-exports + system pages + Form fields) |
|
||||||
| `@repo/ui/form` | `./src/components/Form/index.ts` | Form field components, `withRHF` factory, RHF/Zod re-exports |
|
| `@repo/ui/form` | `./src/components/Form/index.ts` | Form field components, `withRHF` factory, RHF/Zod re-exports |
|
||||||
| `@repo/ui/hooks` | `./src/hooks/index.ts` | Mantine hooks re-export |
|
| `@repo/ui/hooks` | `./src/hooks/index.ts` | Mantine hooks re-export |
|
||||||
@@ -56,12 +56,7 @@ function UserForm() {
|
|||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(console.log)}>
|
<form onSubmit={handleSubmit(console.log)}>
|
||||||
<FieldTextInput name="name" control={control} label="Name" />
|
<FieldTextInput name="name" control={control} label="Name" />
|
||||||
<FieldSelect
|
<FieldSelect name="role" control={control} label="Role" data={['Admin', 'Editor', 'Viewer']} />
|
||||||
name="role"
|
|
||||||
control={control}
|
|
||||||
label="Role"
|
|
||||||
data={['Admin', 'Editor', 'Viewer']}
|
|
||||||
/>
|
|
||||||
<button type="submit">Save</button>
|
<button type="submit">Save</button>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
@@ -77,7 +72,7 @@ The `ActionTools` suite provides flexible, responsive, and semantic action menus
|
|||||||
## Scripts
|
## Scripts
|
||||||
|
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
|---|---|
|
| ----------------- | ----------------------- |
|
||||||
| `pnpm test` | Run unit tests (Vitest) |
|
| `pnpm test` | Run unit tests (Vitest) |
|
||||||
| `pnpm test:watch` | Run tests in watch mode |
|
| `pnpm test:watch` | Run tests in watch mode |
|
||||||
| `pnpm lint` | Run ESLint |
|
| `pnpm lint` | Run ESLint |
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
|
|
||||||
Ensure your local environment matches the following versions to avoid compatibility issues:
|
Ensure your local environment matches the following versions to avoid compatibility issues:
|
||||||
|
|
||||||
* **[Node.js](https://nodejs.org/)**: `v20+` (tested with `v24.11.1`) — required for the `--import tsx` flag used by the desktop prebuild script
|
- **[Node.js](https://nodejs.org/)**: `v20+` (tested with `v24.11.1`) — required for the `--import tsx` flag used by the desktop prebuild script
|
||||||
* **[pnpm](https://pnpm.io/)**: `v8.15.6`
|
- **[pnpm](https://pnpm.io/)**: `v8.15.6`
|
||||||
(Enforced via the `packageManager` field in `package.json`)
|
(Enforced via the `packageManager` field in `package.json`)
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
@@ -29,20 +29,19 @@ This repository uses **[Turborepo](https://turbo.build/repo)** to orchestrate ta
|
|||||||
### Development
|
### Development
|
||||||
|
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
| -------------------- | ---------------------------------------------------------------------------------- |
|
| ------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
||||||
| `pnpm dev` | Start **all applications** (`web` and `docs-dev`) in parallel |
|
| `pnpm dev` | Start **all applications** (`web` and `docs-dev`) in parallel |
|
||||||
| `pnpm dev:web` | Start only the **Main Web App** (strictly at `http://localhost:5173`) |
|
| `pnpm dev:web` | Start only the **Main Web App** (strictly at `http://localhost:5173`) |
|
||||||
| `pnpm dev:landing` | Start the **Public Landing App** (strictly at `http://localhost:3000`) |
|
| `pnpm dev:landing` | Start the **Public Landing App** (strictly at `http://localhost:3000`) |
|
||||||
| `pnpm dev:docs-dev` | Start **[VitePress](https://vitepress.dev/)** for documentation development (strictly at `http://localhost:6060`) |
|
| `pnpm dev:docs-dev` | Start **[VitePress](https://vitepress.dev/)** for documentation development (strictly at `http://localhost:6060`) |
|
||||||
| `pnpm dev:desktop` | Start the **Web App + Electron** in parallel for desktop development |
|
| `pnpm dev:desktop` | Start the **Web App + Electron** in parallel for desktop development |
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE] > **Port Topology**: `electron-vite` dynamically allocates a background port (usually `5174`) for its internal renderer shell during `pnpm dev:desktop`. We strictly isolate `web` (`5173`) and `landing` (`3000`) onto separate port ranges to prevent race conditions during parallel execution.
|
||||||
> **Port Topology**: `electron-vite` dynamically allocates a background port (usually `5174`) for its internal renderer shell during `pnpm dev:desktop`. We strictly isolate `web` (`5173`) and `landing` (`3000`) onto separate port ranges to prevent race conditions during parallel execution.
|
|
||||||
|
|
||||||
### Building & Quality
|
### Building & Quality
|
||||||
|
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
| --------------------- | ------------------------------------------------------- |
|
| --------------------- | ------------------------------------------------------------------ |
|
||||||
| `pnpm build` | Build all apps and packages using Turbo cache |
|
| `pnpm build` | Build all apps and packages using Turbo cache |
|
||||||
| `pnpm build:web` | Build only the web application |
|
| `pnpm build:web` | Build only the web application |
|
||||||
| `pnpm build:landing` | Build only the landing page |
|
| `pnpm build:landing` | Build only the landing page |
|
||||||
@@ -57,14 +56,13 @@ This repository uses **[Turborepo](https://turbo.build/repo)** to orchestrate ta
|
|||||||
To package the application into a production-ready installer, use the following commands from the **root directory**:
|
To package the application into a production-ready installer, use the following commands from the **root directory**:
|
||||||
|
|
||||||
| Command | Platform | Output Artifact |
|
| Command | Platform | Output Artifact |
|
||||||
| ---------------------- | ----------- | ------------------------------------------ |
|
| ---------------------- | ---------- | ---------------------------------------- |
|
||||||
| `pnpm package:desktop` | Current OS | Detects host OS and builds accordingly |
|
| `pnpm package:desktop` | Current OS | Detects host OS and builds accordingly |
|
||||||
| `pnpm package:mac` | macOS | `.dmg` and `.zip` (supports x64 & arm64) |
|
| `pnpm package:mac` | macOS | `.dmg` and `.zip` (supports x64 & arm64) |
|
||||||
| `pnpm package:win` | Windows | `.exe` (NSIS Installer) |
|
| `pnpm package:win` | Windows | `.exe` (NSIS Installer) |
|
||||||
| `pnpm package:linux` | Linux | `.AppImage` |
|
| `pnpm package:linux` | Linux | `.AppImage` |
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT] > **Build Sequence**: All `package:*` commands execute the following pipeline automatically:
|
||||||
> **Build Sequence**: All `package:*` commands execute the following pipeline automatically:
|
|
||||||
>
|
>
|
||||||
> 1. **`turbo run build --filter=web`** — Compiles the React SPA into `apps/web/dist/`.
|
> 1. **`turbo run build --filter=web`** — Compiles the React SPA into `apps/web/dist/`.
|
||||||
> 2. **`prebuild` hook** — Runs `node --import tsx scripts/copy-web-dist.ts`, which copies `apps/web/dist/` → `apps/desktop/web-dist/`.
|
> 2. **`prebuild` hook** — Runs `node --import tsx scripts/copy-web-dist.ts`, which copies `apps/web/dist/` → `apps/desktop/web-dist/`.
|
||||||
@@ -72,8 +70,6 @@ To package the application into a production-ready installer, use the following
|
|||||||
>
|
>
|
||||||
> You do not need to run these steps manually — they are chained via npm scripts.
|
> You do not need to run these steps manually — they are chained via npm scripts.
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING] > **macOS Code Signing**: To build a distributable macOS app with Auto-Update support, you **must** have an Apple Developer Certificate and provide `CSC_LINK` and `CSC_KEY_PASSWORD` in your environment. Without code signing, macOS Gatekeeper will block the app and auto-updates will fail. See the Desktop documentation for details.
|
||||||
> **macOS Code Signing**: To build a distributable macOS app with Auto-Update support, you **must** have an Apple Developer Certificate and provide `CSC_LINK` and `CSC_KEY_PASSWORD` in your environment. Without code signing, macOS Gatekeeper will block the app and auto-updates will fail. See the Desktop documentation for details.
|
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE] > **Cross-Compilation**: It is highly recommended to build for Windows on a Windows machine and for macOS on a Mac. Cross-compilation (e.g., building `.dmg` on Linux) may fail due to platform-specific toolchain dependencies. Use a CI matrix strategy (e.g., GitHub Actions with `runs-on: [macos-latest, windows-latest, ubuntu-latest]`) for multi-platform releases.
|
||||||
> **Cross-Compilation**: It is highly recommended to build for Windows on a Windows machine and for macOS on a Mac. Cross-compilation (e.g., building `.dmg` on Linux) may fail due to platform-specific toolchain dependencies. Use a CI matrix strategy (e.g., GitHub Actions with `runs-on: [macos-latest, windows-latest, ubuntu-latest]`) for multi-platform releases.
|
|
||||||
|
|||||||
Reference in New Issue
Block a user