diff --git a/.agents/skills/coding-standards/SKILL.md b/.agents/skills/coding-standards/SKILL.md
index d9e444a..ca1d043 100644
--- a/.agents/skills/coding-standards/SKILL.md
+++ b/.agents/skills/coding-standards/SKILL.md
@@ -37,8 +37,8 @@ Standards for this TypeScript/React frontend. Not a NestJS API.
### Naming
```typescript
-const searchQuery = 'widget'
-const isAuthenticated = true
+const searchQuery = 'widget';
+const isAuthenticated = true;
async function fetchVehicleType(id: string) {}
function isValidCode(code: string): boolean {}
@@ -47,8 +47,8 @@ function isValidCode(code: string): boolean {}
### Immutability (CRITICAL)
```typescript
-const updated = { ...row, name: 'New' }
-const nextItems = [...items, newItem]
+const updated = { ...row, name: 'New' };
+const nextItems = [...items, newItem];
```
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)
```ts
-import { Button, Text } from '@repo/ui/components'
-import { FieldTextInput } from '@repo/ui/form'
-import { EnterpriseModuleProvider } from '@repo/ui/foundations'
-import { compose, required } from '@repo/ui/validators'
-import { createHttpClient } from '@repo/core-api/http-client'
-import { CommonRemoteDataServices } from '@repo/core-api/data-services'
+import { Button, Text } from '@repo/ui/components';
+import { FieldTextInput } from '@repo/ui/form';
+import { EnterpriseModuleProvider } from '@repo/ui/foundations';
+import { compose, required } from '@repo/ui/validators';
+import { createHttpClient } from '@repo/core-api/http-client';
+import { CommonRemoteDataServices } from '@repo/core-api/data-services';
```
Do not import `@mantine/core` or axios in `apps/web` feature code.
diff --git a/.agents/skills/continuous-learning/SKILL.md b/.agents/skills/continuous-learning/SKILL.md
index 11b0fe4..6eca113 100644
--- a/.agents/skills/continuous-learning/SKILL.md
+++ b/.agents/skills/continuous-learning/SKILL.md
@@ -32,23 +32,19 @@ Edit `config.json` to customize:
"debugging_techniques",
"project_specific"
],
- "ignore_patterns": [
- "simple_typos",
- "one_time_fixes",
- "external_api_issues"
- ]
+ "ignore_patterns": ["simple_typos", "one_time_fixes", "external_api_issues"]
}
```
## Pattern Types
-| Pattern | Description |
-|---------|-------------|
-| `error_resolution` | How specific errors were resolved |
-| `user_corrections` | Patterns from user corrections |
-| `workarounds` | Solutions to framework/library quirks |
-| `debugging_techniques` | Effective debugging approaches |
-| `project_specific` | Project-specific conventions |
+| Pattern | Description |
+| ---------------------- | ------------------------------------- |
+| `error_resolution` | How specific errors were resolved |
+| `user_corrections` | Patterns from user corrections |
+| `workarounds` | Solutions to framework/library quirks |
+| `debugging_techniques` | Effective debugging approaches |
+| `project_specific` | Project-specific conventions |
## Hook Setup
diff --git a/.agents/skills/detail-layout/SKILL.md b/.agents/skills/detail-layout/SKILL.md
index e259b2b..64684dd 100644
--- a/.agents/skills/detail-layout/SKILL.md
+++ b/.agents/skills/detail-layout/SKILL.md
@@ -32,11 +32,11 @@ 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:
-| 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 |
+| 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 |
| 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) |
Once a page picks stacked vs tabbed, stay consistent — don't mix an ad-hoc tab region with an ad-hoc long scroll of the same categories.
@@ -67,8 +67,8 @@ Use when the entity has a meaningful visual (device photo, avatar, logo). Skip f
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) |
- Media left-aligned; title stack left-aligned next to media; any block-level action right-aligned on the same row.
@@ -203,25 +203,25 @@ Horizontal gaps inside grids match the medium rhythm. Don't invent a fourth gap
## 13. Responsive collapse
-| Wide layout | Narrow collapse |
-| --- | --- |
-| 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) |
-| Status metric horizontal row (§6) | Stacked metric sub-blocks; badge beside value |
-| Summary 2–3 columns (§7) | Stacked column groups, same order |
-| Tab list | Horizontally scrollable tab list; panels still one-at-a-time |
-| Table | Horizontal scroll inside the section **or** stacked label/value per row — pick one strategy per table type and keep it project-wide |
+| Wide layout | Narrow collapse |
+| -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
+| 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) |
+| Status metric horizontal row (§6) | Stacked metric sub-blocks; badge beside value |
+| Summary 2–3 columns (§7) | Stacked column groups, same order |
+| Tab list | Horizontally scrollable tab list; panels still one-at-a-time |
+| Table | Horizontal scroll inside the section **or** stacked label/value per row — pick one strategy per table type and keep it project-wide |
Never re-pair fields into different logical groups on collapse — only column count / orientation changes.
## 14. Action placement (position only)
-| Action scope | Position |
-| --- | --- |
-| 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 filters | Right side of the table/section header, before or beside the section primary action |
-| Row action | Trailing column of that row only |
+| Action scope | Position |
+| ------------------------------------------------- | ----------------------------------------------------------------------------------- |
+| 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 filters | Right side of the table/section header, before or beside the section primary action |
+| Row action | Trailing column of that row only |
Do not duplicate Edit in the body if the provider already exposes it, unless a mobile identity block needs a full-width local affordance (§3 narrow).
diff --git a/.agents/skills/eval-harness/SKILL.md b/.agents/skills/eval-harness/SKILL.md
index 7f088eb..e79af72 100644
--- a/.agents/skills/eval-harness/SKILL.md
+++ b/.agents/skills/eval-harness/SKILL.md
@@ -5,6 +5,7 @@ A formal evaluation framework for Claude Code sessions, implementing eval-driven
## Philosophy
Eval-Driven Development treats evals as the "unit tests of AI development":
+
- Define expected behavior BEFORE implementation
- Run evals continuously during development
- Track regressions with each change
@@ -13,33 +14,41 @@ Eval-Driven Development treats evals as the "unit tests of AI development":
## Eval Types
### Capability Evals
+
Test if Claude can do something it couldn't before:
+
```markdown
[CAPABILITY EVAL: feature-name]
Task: Description of what Claude should accomplish
Success Criteria:
- - [ ] Criterion 1
- - [ ] Criterion 2
- - [ ] Criterion 3
-Expected Output: Description of expected result
+
+- [ ] Criterion 1
+- [ ] Criterion 2
+- [ ] Criterion 3
+ Expected Output: Description of expected result
```
### Regression Evals
+
Ensure changes don't break existing functionality:
+
```markdown
[REGRESSION EVAL: feature-name]
Baseline: SHA or checkpoint name
Tests:
- - existing-test-1: PASS/FAIL
- - existing-test-2: PASS/FAIL
- - existing-test-3: PASS/FAIL
-Result: X/Y passed (previously Y/Y)
+
+- existing-test-1: PASS/FAIL
+- existing-test-2: PASS/FAIL
+- existing-test-3: PASS/FAIL
+ Result: X/Y passed (previously Y/Y)
```
## Grader Types
### 1. Code-Based Grader
+
Deterministic checks using code:
+
```bash
# Check if file contains expected pattern
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
+
Use Claude to evaluate open-ended outputs:
+
```markdown
[MODEL GRADER PROMPT]
Evaluate the following code change:
+
1. Does it solve the stated problem?
2. Is it well-structured?
3. Are edge cases handled?
@@ -66,7 +78,9 @@ Reasoning: [explanation]
```
### 3. Human Grader
+
Flag for manual review:
+
```markdown
[HUMAN REVIEW REQUIRED]
Change: Description of what changed
@@ -77,13 +91,17 @@ Risk Level: LOW/MEDIUM/HIGH
## Metrics
### pass@k
+
"At least one success in k attempts"
+
- pass@1: First attempt success rate
- pass@3: Success within 3 attempts
- Typical target: pass@3 > 90%
### pass^k
+
"All k trials succeed"
+
- Higher bar for reliability
- pass^3: 3 consecutive successes
- Use for critical paths
@@ -91,28 +109,34 @@ Risk Level: LOW/MEDIUM/HIGH
## Eval Workflow
### 1. Define (Before Coding)
+
```markdown
## EVAL DEFINITION: feature-xyz
### Capability Evals
+
1. Can create new user account
2. Can validate email format
3. Can hash password securely
### Regression Evals
+
1. Existing login still works
2. Session management unchanged
3. Logout flow intact
### Success Metrics
+
- pass@3 > 90% for capability evals
- pass^3 = 100% for regression evals
```
### 2. Implement
+
Write code to pass the defined evals.
### 3. Evaluate
+
```bash
# Run capability evals
[Run each capability eval, record PASS/FAIL]
@@ -124,25 +148,25 @@ npm test -- --testPathPattern="existing"
```
### 4. Report
+
```markdown
-EVAL REPORT: feature-xyz
-========================
+# EVAL REPORT: feature-xyz
Capability Evals:
- create-user: PASS (pass@1)
- validate-email: PASS (pass@2)
- hash-password: PASS (pass@1)
- Overall: 3/3 passed
+create-user: PASS (pass@1)
+validate-email: PASS (pass@2)
+hash-password: PASS (pass@1)
+Overall: 3/3 passed
Regression Evals:
- login-flow: PASS
- session-mgmt: PASS
- logout-flow: PASS
- Overall: 3/3 passed
+login-flow: PASS
+session-mgmt: PASS
+logout-flow: PASS
+Overall: 3/3 passed
Metrics:
- pass@1: 67% (2/3)
- pass@3: 100% (3/3)
+pass@1: 67% (2/3)
+pass@3: 100% (3/3)
Status: READY FOR REVIEW
```
@@ -150,26 +174,33 @@ Status: READY FOR REVIEW
## Integration Patterns
### Pre-Implementation
+
```
/eval define feature-name
```
+
Creates eval definition file at `.cursor/evals/feature-name.md`
### During Implementation
+
```
/eval check feature-name
```
+
Runs current evals and reports status
### Post-Implementation
+
```
/eval report feature-name
```
+
Generates full eval report
## Eval Storage
Store evals in project:
+
```
.cursor/
evals/
@@ -194,7 +225,9 @@ Store evals in project:
## EVAL: add-authentication
### Phase 1: Define (10 min)
+
Capability Evals:
+
- [ ] User can register with email/password
- [ ] User can login with valid credentials
- [ ] Invalid credentials rejected with proper error
@@ -202,19 +235,23 @@ Capability Evals:
- [ ] Logout clears session
Regression Evals:
+
- [ ] Public routes still accessible
- [ ] API responses unchanged
- [ ] Database schema compatible
### Phase 2: Implement (varies)
+
[Write code]
### Phase 3: Evaluate
+
Run: /eval check add-authentication
### Phase 4: Report
-EVAL REPORT: add-authentication
-==============================
+
+# EVAL REPORT: add-authentication
+
Capability: 5/5 passed (pass@3: 100%)
Regression: 3/3 passed (pass^3: 100%)
Status: SHIP IT
diff --git a/.agents/skills/project-guidelines-example/SKILL.md b/.agents/skills/project-guidelines-example/SKILL.md
index a5d6def..87070de 100644
--- a/.agents/skills/project-guidelines-example/SKILL.md
+++ b/.agents/skills/project-guidelines-example/SKILL.md
@@ -82,7 +82,7 @@ Auth lives under `src/apps/auth/`. Shared-across-modules code lives in `src/core
### Module config + factory
```typescript
-import { ModuleConfigEntity } from '@repo/ui/foundations'
+import { ModuleConfigEntity } from '@repo/ui/foundations';
export const fullPageModuleConfig: ModuleConfigEntity = {
moduleKey: 'EXAMPLE_FULL_PAGE',
@@ -91,28 +91,28 @@ export const fullPageModuleConfig: ModuleConfigEntity = {
webUrl: '/app/example/full-page',
moduleCategory: 'FULL_PAGE',
moduleType: 'MASTER_DATA',
-}
+};
```
```tsx
-import { EnterpriseModuleProvider } from '@repo/ui/foundations'
-import { registerModuleNamespace } from '@repo/core-i18n'
-import { apiClient } from '../../../../../../../core/lib/api-client'
+import { EnterpriseModuleProvider } from '@repo/ui/foundations';
+import { registerModuleNamespace } from '@repo/core-i18n';
+import { apiClient } from '../../../../../../../core/lib/api-client';
-registerModuleNamespace(fullPageModuleConfig.translationNamespace, { id, en })
+registerModuleNamespace(fullPageModuleConfig.translationNamespace, { id, en });
```
### Validators (Zod)
```typescript
-import { z } from 'zod'
-import { compose, required, rangeLength } from '@repo/ui/validators'
+import { z } from 'zod';
+import { compose, required, rangeLength } from '@repo/ui/validators';
export const createFullPageSchema = (t: (key: string) => string) =>
z.object({
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'))),
- })
+ });
```
### Forms and HTTP
diff --git a/.agents/skills/security-review/SKILL.md b/.agents/skills/security-review/SKILL.md
index bdd82fc..ca7b0ab 100644
--- a/.agents/skills/security-review/SKILL.md
+++ b/.agents/skills/security-review/SKILL.md
@@ -21,11 +21,11 @@ Client-side security for this React/Electron frontend. There is no NestJS API or
```typescript
// NEVER
-const apiKey = "sk-proj-xxxxx"
+const apiKey = 'sk-proj-xxxxx';
// ALWAYS
-import { ENV } from '../environment'
-if (!ENV.API_BASE_URL) throw new Error('VITE_API_BASE_URL is not configured')
+import { ENV } from '../environment';
+if (!ENV.API_BASE_URL) throw new Error('VITE_API_BASE_URL is not configured');
```
- [ ] No hardcoded secrets
diff --git a/.agents/skills/strategic-compact/SKILL.md b/.agents/skills/strategic-compact/SKILL.md
index 7503d30..a187e15 100644
--- a/.agents/skills/strategic-compact/SKILL.md
+++ b/.agents/skills/strategic-compact/SKILL.md
@@ -10,11 +10,13 @@ Suggests manual `/compact` at strategic points in your workflow rather than rely
## Why Strategic Compaction?
Auto-compaction triggers at arbitrary points:
+
- Often mid-task, losing important context
- No awareness of logical task boundaries
- Can interrupt complex multi-step operations
Strategic compaction at logical boundaries:
+
- **After exploration, before execution** - Compact research context, keep implementation plan
- **After completing a milestone** - Fresh start for next phase
- **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
Environment variables:
+
- `COMPACT_THRESHOLD` - Tool calls before first suggestion (default: 50)
## Best Practices
@@ -54,7 +57,7 @@ Environment variables:
1. **Compact after planning** - Once plan is finalized, compact to start fresh
2. **Compact after debugging** - Clear error-resolution context before continuing
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
diff --git a/.agents/skills/tdd-workflow/SKILL.md b/.agents/skills/tdd-workflow/SKILL.md
index cf0d300..3b34fe2 100644
--- a/.agents/skills/tdd-workflow/SKILL.md
+++ b/.agents/skills/tdd-workflow/SKILL.md
@@ -38,31 +38,31 @@ TDD for this frontend monorepo (Vitest, not NestJS/Supertest).
## Unit example
```typescript
-import { describe, it, expect } from 'vitest'
-import { createFullPageSchema } from './full-page.validator'
+import { describe, it, expect } from 'vitest';
+import { createFullPageSchema } from './full-page.validator';
describe('createFullPageSchema', () => {
- const t = (key: string) => key
+ const t = (key: string) => key;
it('rejects an empty code', () => {
- const result = createFullPageSchema(t).safeParse({ code: '', name: 'Widget' })
- expect(result.success).toBe(false)
- })
-})
+ const result = createFullPageSchema(t).safeParse({ code: '', name: 'Widget' });
+ expect(result.success).toBe(false);
+ });
+});
```
## Component example
```tsx
-import { render, screen } from '@testing-library/react'
-import userEvent from '@testing-library/user-event'
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
it('calls onClick', async () => {
- const onClick = vi.fn()
- render()
- await userEvent.click(screen.getByRole('button', { name: 'Save' }))
- expect(onClick).toHaveBeenCalledTimes(1)
-})
+ const onClick = vi.fn();
+ render();
+ await userEvent.click(screen.getByRole('button', { name: 'Save' }));
+ expect(onClick).toHaveBeenCalledTimes(1);
+});
```
## Mocking
@@ -72,7 +72,7 @@ Mock `@repo/core-api` and `apiClient`, not a database.
```ts
vi.mock('@repo/core-api/http-client', () => ({
createHttpClient: () => ({ get: vi.fn(), post: vi.fn(), put: vi.fn(), delete: vi.fn() }),
-}))
+}));
```
## File organization
diff --git a/.agents/skills/verification-loop/SKILL.md b/.agents/skills/verification-loop/SKILL.md
index b56bb7e..bb6fd8c 100644
--- a/.agents/skills/verification-loop/SKILL.md
+++ b/.agents/skills/verification-loop/SKILL.md
@@ -5,6 +5,7 @@ A comprehensive verification system for Claude Code sessions.
## When to Use
Invoke this skill:
+
- After completing a feature or significant code change
- Before creating a PR
- When you want to ensure quality gates pass
@@ -13,6 +14,7 @@ Invoke this skill:
## Verification Phases
### Phase 1: Build Verification
+
```bash
# Check if project builds
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.
### Phase 2: Type Check
+
```bash
# TypeScript projects
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.
### Phase 3: Lint Check
+
```bash
# JavaScript/TypeScript
npm run lint 2>&1 | head -30
@@ -43,6 +47,7 @@ ruff check . 2>&1 | head -30
```
### Phase 4: Test Suite
+
```bash
# Run tests with coverage
npm run test -- --coverage 2>&1 | tail -50
@@ -52,12 +57,14 @@ npm run test -- --coverage 2>&1 | tail -50
```
Report:
+
- Total tests: X
- Passed: X
- Failed: X
- Coverage: X%
### Phase 5: Security Scan
+
```bash
# Check for secrets
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
+
```bash
# Show what changed
git diff --stat
@@ -75,6 +83,7 @@ git diff HEAD~1 --name-only
```
Review each changed file for:
+
- Unintended changes
- Missing error handling
- Potential edge cases
@@ -107,6 +116,7 @@ For long sessions, run verification every 15 minutes or after major changes:
```markdown
Set a mental checkpoint:
+
- After completing each function
- After finishing a component
- Before moving to next task
diff --git a/.cursor/agents/architect.md b/.cursor/agents/architect.md
index fabfbad..5bef72c 100644
--- a/.cursor/agents/architect.md
+++ b/.cursor/agents/architect.md
@@ -19,18 +19,21 @@ You are a senior software architect specializing in scalable, maintainable syste
## Architecture Review Process
### 1. Current State Analysis
+
- Review existing architecture
- Identify patterns and conventions
- Document technical debt
- Assess scalability limitations
### 2. Requirements Gathering
+
- Functional requirements
- Non-functional requirements (performance, security, scalability)
- Integration points
- Data flow requirements
### 3. Design Proposal
+
- High-level architecture diagram
- Component responsibilities
- Data models
@@ -38,7 +41,9 @@ You are a senior software architect specializing in scalable, maintainable syste
- Integration patterns
### 4. Trade-Off Analysis
+
For each design decision, document:
+
- **Pros**: Benefits and advantages
- **Cons**: Drawbacks and limitations
- **Alternatives**: Other options considered
@@ -47,12 +52,14 @@ For each design decision, document:
## Architectural Principles
### 1. Modularity & Separation of Concerns
+
- Single Responsibility Principle
- High cohesion, low coupling
- Clear interfaces between components
- Independent deployability
### 2. Scalability
+
- Horizontal scaling capability
- Stateless design where possible
- Efficient database queries
@@ -60,6 +67,7 @@ For each design decision, document:
- Load balancing considerations
### 3. Maintainability
+
- Clear code organization
- Consistent patterns
- Comprehensive documentation
@@ -67,6 +75,7 @@ For each design decision, document:
- Simple to understand
### 4. Security
+
- Defense in depth
- Principle of least privilege
- Input validation at boundaries
@@ -74,6 +83,7 @@ For each design decision, document:
- Audit trail
### 5. Performance
+
- Efficient algorithms
- Minimal network requests
- Optimized database queries
@@ -83,6 +93,7 @@ For each design decision, document:
## Common Patterns
### Frontend Patterns
+
- **Component Composition**: Build complex UI from simple components
- **Container/Presenter**: Separate data logic from presentation
- **Custom Hooks**: Reusable stateful logic
@@ -90,6 +101,7 @@ For each design decision, document:
- **Code Splitting**: Lazy load routes and heavy components
### Backend Patterns
+
- **Repository Pattern**: Abstract data access
- **Service Layer**: Business logic separation
- **Middleware Pattern**: Request/response processing
@@ -97,6 +109,7 @@ For each design decision, document:
- **CQRS**: Separate read and write operations
### Data Patterns
+
- **Normalized Database**: Reduce redundancy
- **Denormalized for Read Performance**: Optimize queries
- **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
## Context
+
Need a default place for product screens vs reusable components.
## 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/`.
## Consequences
### Positive
+
- Clear promotion path: module → core → package
- Showcase and docs stay free of product logic
### Negative
+
- Easy to over-share too early (YAGNI)
### Alternatives Considered
+
- All UI in apps/web (duplicates landing/showcase)
- All features in packages/ui (mixes product with design system)
## Status
+
Accepted
```
@@ -138,18 +157,21 @@ Accepted
When designing a new system or feature:
### Functional Requirements
+
- [ ] User stories documented
- [ ] API contracts defined
- [ ] Data models specified
- [ ] UI/UX flows mapped
### Non-Functional Requirements
+
- [ ] Performance targets defined (latency, throughput)
- [ ] Scalability requirements specified
- [ ] Security requirements identified
- [ ] Availability targets set (uptime %)
### Technical Design
+
- [ ] Architecture diagram created
- [ ] Component responsibilities defined
- [ ] Data flow documented
@@ -158,6 +180,7 @@ When designing a new system or feature:
- [ ] Testing strategy planned
### Operations
+
- [ ] Deployment strategy defined
- [ ] Monitoring and alerting planned
- [ ] Backup and recovery strategy
@@ -166,6 +189,7 @@ When designing a new system or feature:
## Red Flags
Watch for these architectural anti-patterns:
+
- **Big Ball of Mud**: No clear structure
- **Golden Hammer**: Using same solution for everything
- **Premature Optimization**: Optimizing too early
diff --git a/.cursor/agents/code-reviewer.md b/.cursor/agents/code-reviewer.md
index 0bba652..796445f 100644
--- a/.cursor/agents/code-reviewer.md
+++ b/.cursor/agents/code-reviewer.md
@@ -8,11 +8,13 @@ model: opus
You are a senior code reviewer ensuring high standards of code quality and security.
When invoked:
+
1. Run git diff to see recent changes
2. Focus on modified files
3. Begin review immediately
Review checklist:
+
- Code is simple and readable
- Functions and variables are well-named
- No duplicated code
@@ -25,6 +27,7 @@ Review checklist:
- Licenses of integrated libraries checked
Provide feedback organized by priority:
+
- Critical issues (must fix)
- Warnings (should fix)
- Suggestions (consider improving)
@@ -74,6 +77,7 @@ Include specific examples of how to fix issues.
## Review Output Format
For each issue:
+
```
[CRITICAL] Hardcoded API key
File: src/core/lib/api-client.ts:42
diff --git a/.cursor/agents/e2e-runner.md b/.cursor/agents/e2e-runner.md
index 830fe12..e798dc2 100644
--- a/.cursor/agents/e2e-runner.md
+++ b/.cursor/agents/e2e-runner.md
@@ -42,14 +42,14 @@ Canonical sample: `apps/web/src/apps/main/modules/example/full-page/`. Copy that
### Package component tests (Testing Library)
```tsx
-import { render, screen } from '@testing-library/react'
-import userEvent from '@testing-library/user-event'
-import { FieldTextInput } from '@repo/ui/form'
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { FieldTextInput } from '@repo/ui/form';
it('renders the field label', () => {
- render()
- expect(screen.getByLabelText('Code')).toBeInTheDocument()
-})
+ render();
+ expect(screen.getByLabelText('Code')).toBeInTheDocument();
+});
```
### Mock remote data services (not a database)
@@ -63,7 +63,7 @@ vi.mock('../../domain/factories', () => ({
update: vi.fn(),
delete: vi.fn(),
},
-}))
+}));
```
## Browser verification (`apps/web`)
@@ -92,9 +92,11 @@ Do not add Playwright unless the user explicitly asks.
**Command:** pnpm test
## Summary
+
- Total / passed / failed
## Failed
+
- File — assertion
- Recommended fix
```
diff --git a/.cursor/agents/planner.md b/.cursor/agents/planner.md
index 8d4ad52..b5aec1b 100644
--- a/.cursor/agents/planner.md
+++ b/.cursor/agents/planner.md
@@ -18,19 +18,23 @@ You are an expert planning specialist focused on creating comprehensive, actiona
## Planning Process
### 1. Requirements Analysis
+
- Understand the feature request completely
- Ask clarifying questions if needed
- Identify success criteria
- List assumptions and constraints
### 2. Architecture Review
+
- Analyze existing codebase structure
- Identify affected components
- Review similar implementations
- Consider reusable patterns
### 3. Step Breakdown
+
Create detailed steps with:
+
- Clear, specific actions
- File paths and locations
- Dependencies between steps
@@ -38,6 +42,7 @@ Create detailed steps with:
- Potential risks
### 4. Implementation Order
+
- Prioritize by dependencies
- Group related changes
- Minimize context switching
@@ -49,20 +54,25 @@ Create detailed steps with:
# Implementation Plan: [Feature Name]
## Overview
+
[2-3 sentence summary]
## Requirements
+
- [Requirement 1]
- [Requirement 2]
## Architecture Changes
+
- [Change 1: file path and description]
- [Change 2: file path and description]
## Implementation Steps
### Phase 1: [Phase Name]
+
1. **[Step Name]** (File: path/to/file.ts)
+
- Action: Specific action to take
- Why: Reason for this step
- Dependencies: None / Requires step X
@@ -72,18 +82,22 @@ Create detailed steps with:
...
### Phase 2: [Phase Name]
+
...
## Testing Strategy
+
- Unit tests: [files to test]
- Integration tests: [flows to test]
- E2E tests: [user journeys to test]
## Risks & Mitigations
+
- **Risk**: [Description]
- Mitigation: [How to address]
## Success Criteria
+
- [ ] Criterion 1
- [ ] Criterion 2
```
diff --git a/.cursor/agents/refactor-cleaner.md b/.cursor/agents/refactor-cleaner.md
index f9d5079..401d420 100644
--- a/.cursor/agents/refactor-cleaner.md
+++ b/.cursor/agents/refactor-cleaner.md
@@ -20,12 +20,14 @@ You are an expert refactoring specialist focused on code cleanup and consolidati
## Tools at Your Disposal
### Detection Tools
+
- **knip** - Find unused files, exports, dependencies, types
- **depcheck** - Identify unused npm dependencies
- **ts-prune** - Find unused TypeScript exports
- **eslint** - Check for unused disable-directives and variables
### Analysis Commands
+
```bash
# Run knip for unused exports/files/dependencies
npx knip
@@ -43,6 +45,7 @@ npx eslint . --report-unused-disable-directives
## Refactoring Workflow
### 1. Analysis Phase
+
```
a) Run detection tools in parallel
b) Collect all findings
@@ -53,6 +56,7 @@ c) Categorize by risk level:
```
### 2. Risk Assessment
+
```
For each item to remove:
- Check if it's imported anywhere (grep search)
@@ -63,6 +67,7 @@ For each item to remove:
```
### 3. Safe Removal Process
+
```
a) Start with SAFE items only
b) Remove one category at a time:
@@ -75,6 +80,7 @@ d) Create git commit for each batch
```
### 4. Duplicate Consolidation
+
```
a) Find duplicate components/utilities
b) Choose the best implementation:
@@ -96,28 +102,34 @@ Create/update `docs/DELETION_LOG.md` with this structure:
## [YYYY-MM-DD] Refactor Session
### Unused Dependencies Removed
+
- package-name@version - Last used: never, Size: XX KB
- another-package@version - Replaced by: better-package
### Unused Files Deleted
+
- src/old-component.tsx - Replaced by: src/new-component.tsx
- lib/deprecated-util.ts - Functionality moved to: lib/utils.ts
### Duplicate Code Consolidated
+
- src/components/Button1.tsx + Button2.tsx → Button.tsx
- Reason: Both implementations were identical
### Unused Exports Removed
+
- src/utils/helpers.ts - Functions: foo(), bar()
- Reason: No references found in codebase
### Impact
+
- Files deleted: 15
- Dependencies removed: 5
- Lines of code removed: 2,300
- Bundle size reduction: ~45 KB
### Testing
+
- All unit tests passing: ✓
- All integration tests passing: ✓
- Manual testing completed: ✓
@@ -126,6 +138,7 @@ Create/update `docs/DELETION_LOG.md` with this structure:
## Safety Checklist
Before removing ANYTHING:
+
- [ ] Run detection tools
- [ ] Grep for all references
- [ ] Check dynamic imports
@@ -136,6 +149,7 @@ Before removing ANYTHING:
- [ ] Document in DELETION_LOG.md
After each removal:
+
- [ ] Build succeeds
- [ ] Tests pass
- [ ] No console errors
@@ -145,20 +159,22 @@ After each removal:
## Common Patterns to Remove
### 1. Unused Imports
+
```typescript
// ❌ 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
-import { useState } from 'react'
+import { useState } from 'react';
```
### 2. Dead Code Branches
+
```typescript
// ❌ Remove unreachable code
if (false) {
// This never executes
- doSomething()
+ doSomething();
}
// ❌ Remove unused functions
@@ -168,6 +184,7 @@ export function unusedHelper() {
```
### 3. Duplicate Components
+
```typescript
// ❌ Multiple similar components
components/Button.tsx
@@ -179,12 +196,13 @@ components/Button.tsx (with variant prop)
```
### 4. Unused Dependencies
+
```json
// ❌ Package installed but not imported
{
"dependencies": {
- "lodash": "^4.17.21", // Not used anywhere
- "moment": "^2.29.4" // Replaced by date-fns
+ "lodash": "^4.17.21", // Not used anywhere
+ "moment": "^2.29.4" // Replaced by date-fns
}
}
```
@@ -192,6 +210,7 @@ components/Button.tsx (with variant prop)
## Example Project-Specific Rules
**CRITICAL - NEVER REMOVE:**
+
- `apiClient` / `createHttpClient` wiring
- `terminateAuthSession` / auth interceptors
- `EnterpriseModuleProvider` and FULL_PAGE page providers
@@ -199,6 +218,7 @@ components/Button.tsx (with variant prop)
- Electron preload / IPC bridge
**SAFE TO REMOVE:**
+
- Old unused components in components/ folder
- Deprecated utility functions
- Test files for deleted features
@@ -206,6 +226,7 @@ components/Button.tsx (with variant prop)
- Unused TypeScript types/interfaces
**ALWAYS VERIFY:**
+
- Auth login + `terminateAuthSession`
- `example/full-page` still routes and loads
- `@repo/ui` exports used by web/showcase
@@ -219,26 +240,31 @@ When opening PR with deletions:
## Refactor: Code Cleanup
### Summary
+
Dead code cleanup removing unused exports, dependencies, and duplicates.
### Changes
+
- Removed X unused files
- Removed Y unused dependencies
- Consolidated Z duplicate components
- See docs/DELETION_LOG.md for details
### Testing
+
- [x] Build passes
- [x] All tests pass
- [x] Manual testing completed
- [x] No console errors
### Impact
+
- Bundle size: -XX KB
- Lines of code: -XXXX
- Dependencies: -X packages
### Risk Level
+
🟢 LOW - Only removed verifiably unused code
See DELETION_LOG.md for complete details.
@@ -249,6 +275,7 @@ See DELETION_LOG.md for complete details.
If something breaks after removal:
1. **Immediate rollback:**
+
```bash
git revert HEAD
pnpm install
@@ -257,11 +284,13 @@ If something breaks after removal:
```
2. **Investigate:**
+
- What failed?
- Was it a dynamic import?
- Was it used in a way detection tools missed?
3. **Fix forward:**
+
- Mark item as "DO NOT REMOVE" in notes
- Document why detection tools missed it
- Add explicit type annotations if needed
@@ -293,6 +322,7 @@ If something breaks after removal:
## Success Metrics
After cleanup session:
+
- ✅ All tests passing
- ✅ Build succeeds
- ✅ No console errors
diff --git a/.cursor/agents/security-reviewer.md b/.cursor/agents/security-reviewer.md
index c627381..6b51915 100644
--- a/.cursor/agents/security-reviewer.md
+++ b/.cursor/agents/security-reviewer.md
@@ -65,7 +65,10 @@ If CRITICAL: stop, fix, rotate any leaked secret, scan for the same pattern.
```markdown
# Security Review
+
**Status:** CLEAR / ISSUES FOUND
+
## Critical / High / Medium
+
- File:line — issue — fix
```
diff --git a/.cursor/agents/tdd-guide.md b/.cursor/agents/tdd-guide.md
index c8909c2..71cf3fc 100644
--- a/.cursor/agents/tdd-guide.md
+++ b/.cursor/agents/tdd-guide.md
@@ -19,18 +19,18 @@ You are a Test-Driven Development (TDD) specialist. This repo uses **Vitest** (a
### Step 1: Write the test first (RED)
```typescript
-import { describe, it, expect } from 'vitest'
-import { createFullPageSchema } from './full-page.validator'
+import { describe, it, expect } from 'vitest';
+import { createFullPageSchema } from './full-page.validator';
describe('createFullPageSchema', () => {
- const t = (key: string) => key
+ const t = (key: string) => key;
it('rejects an empty code', () => {
- const schema = createFullPageSchema(t)
- const result = schema.safeParse({ code: '', name: 'Widget' })
- expect(result.success).toBe(false)
- })
-})
+ const schema = createFullPageSchema(t);
+ const result = schema.safeParse({ code: '', name: 'Widget' });
+ expect(result.success).toBe(false);
+ });
+});
```
### Step 2: Run it (must FAIL)
@@ -47,7 +47,7 @@ export const createFullPageSchema = (t: (key: string) => string) =>
z.object({
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'))),
- })
+ });
```
### 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
vi.mock('@repo/core-api/http-client', () => ({
createHttpClient: () => ({ get: vi.fn(), post: vi.fn(), put: vi.fn(), delete: vi.fn() }),
-}))
+}));
```
## Edge cases you MUST test
diff --git a/.cursor/commands/build-fix.md b/.cursor/commands/build-fix.md
index 2045c5d..ae0a460 100644
--- a/.cursor/commands/build-fix.md
+++ b/.cursor/commands/build-fix.md
@@ -5,16 +5,19 @@ Incrementally fix TypeScript and build errors:
1. Run `pnpm typecheck` or `pnpm build` (this repo uses pnpm + Turbo + Vite)
2. Parse error output:
+
- Group by file
- Sort by severity
3. For each error:
+
- Show 5 lines of context
- Explain the issue
- Apply a minimal fix
- Re-run the failing command
4. Stop if:
+
- The fix introduces new errors
- The same error persists after 3 attempts
- The user asks to pause
diff --git a/.cursor/commands/checkpoint.md b/.cursor/commands/checkpoint.md
index b835a75..c69cd8d 100644
--- a/.cursor/commands/checkpoint.md
+++ b/.cursor/commands/checkpoint.md
@@ -26,12 +26,14 @@ When verifying against a checkpoint:
1. Read checkpoint from log
2. Compare current state to checkpoint:
+
- Files added since checkpoint
- Files modified since checkpoint
- Test pass rate now vs then
- Coverage now vs then
3. Report:
+
```
CHECKPOINT COMPARISON: $NAME
============================
@@ -44,6 +46,7 @@ Build: [PASS/FAIL]
## List Checkpoints
Show all checkpoints with:
+
- Name
- Timestamp
- Git SHA
@@ -68,6 +71,7 @@ Typical checkpoint flow:
## Arguments
$ARGUMENTS:
+
- `create ` - Create named checkpoint
- `verify ` - Verify against named checkpoint
- `list` - Show all checkpoints
diff --git a/.cursor/commands/code-review.md b/.cursor/commands/code-review.md
index 4e5ef01..31bc66f 100644
--- a/.cursor/commands/code-review.md
+++ b/.cursor/commands/code-review.md
@@ -7,14 +7,16 @@ Comprehensive security and quality review of uncommitted changes:
2. For each changed file, check for:
**Security Issues (CRITICAL):**
+
- Hardcoded credentials, API keys, tokens
- SQL injection vulnerabilities
-- XSS vulnerabilities
+- XSS vulnerabilities
- Missing input validation
- Insecure dependencies
- Path traversal risks
**Code Quality (HIGH):**
+
- Functions > 50 lines
- Files > 800 lines
- Nesting depth > 4 levels
@@ -24,12 +26,14 @@ Comprehensive security and quality review of uncommitted changes:
- Missing JSDoc for public APIs
**Best Practices (MEDIUM):**
+
- Mutation patterns (use immutable instead)
- Emoji usage in code/comments
- Missing tests for new code
- Accessibility issues (a11y)
3. Generate report with:
+
- Severity: CRITICAL, HIGH, MEDIUM, LOW
- File location and line numbers
- Issue description
diff --git a/.cursor/commands/eval.md b/.cursor/commands/eval.md
index 1c788e9..b93d4d6 100644
--- a/.cursor/commands/eval.md
+++ b/.cursor/commands/eval.md
@@ -16,17 +16,21 @@ Create a new eval definition:
```markdown
## EVAL: feature-name
+
Created: $(date)
### Capability Evals
+
- [ ] [Description of capability 1]
- [ ] [Description of capability 2]
### Regression Evals
+
- [ ] [Existing behavior 1 still works]
- [ ] [Existing behavior 2 still works]
### Success Criteria
+
- pass@3 > 90% for capability evals
- pass^3 = 100% for regression evals
```
@@ -113,6 +117,7 @@ feature-export [0/4 passing] NOT STARTED
## Arguments
$ARGUMENTS:
+
- `define ` - Create new eval definition
- `check ` - Run and check evals
- `report ` - Generate full report
diff --git a/.cursor/commands/learn.md b/.cursor/commands/learn.md
index f563a2b..c70217b 100644
--- a/.cursor/commands/learn.md
+++ b/.cursor/commands/learn.md
@@ -11,17 +11,20 @@ Run `/learn` at any point during a session when you've solved a non-trivial prob
Look for:
1. **Error Resolution Patterns**
+
- What error occurred?
- What was the root cause?
- What fixed it?
- Is this reusable for similar errors?
2. **Debugging Techniques**
+
- Non-obvious debugging steps
- Tool combinations that worked
- Diagnostic patterns
3. **Workarounds**
+
- Library quirks
- API limitations
- 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]
## Problem
+
[What problem this solves - be specific]
## Solution
+
[The pattern/technique/workaround]
## Example
+
[Code example if applicable]
## When to Use
+
[Trigger conditions - what should activate this skill]
```
diff --git a/.cursor/commands/orchestrate.md b/.cursor/commands/orchestrate.md
index 30ac2b8..6a14cd2 100644
--- a/.cursor/commands/orchestrate.md
+++ b/.cursor/commands/orchestrate.md
@@ -9,25 +9,33 @@ Sequential agent workflow for complex tasks.
## Workflow Types
### feature
+
Full feature implementation workflow:
+
```
planner -> tdd-guide -> code-reviewer -> security-reviewer
```
### bugfix
+
Bug investigation and fix workflow:
+
```
explorer -> tdd-guide -> code-reviewer
```
### refactor
+
Safe refactoring workflow:
+
```
architect -> code-reviewer -> tdd-guide
```
### security
+
Security-focused review:
+
```
security-reviewer -> code-reviewer -> architect
```
@@ -49,18 +57,23 @@ Between agents, create handoff document:
## HANDOFF: [previous-agent] -> [next-agent]
### Context
+
[Summary of what was done]
### Findings
+
[Key discoveries or decisions]
### Files Modified
+
[List of files touched]
### Open Questions
+
[Unresolved items for next agent]
### Recommendations
+
[Suggested next steps]
```
@@ -73,18 +86,21 @@ Between agents, create handoff document:
Executes:
1. **Planner Agent**
+
- Analyzes requirements
- Creates implementation plan
- Identifies dependencies
- Output: `HANDOFF: planner -> tdd-guide`
2. **TDD Guide Agent**
+
- Reads planner handoff
- Writes tests first
- Implements to pass tests
- Output: `HANDOFF: tdd-guide -> code-reviewer`
3. **Code Reviewer Agent**
+
- Reviews implementation
- Checks for issues
- Suggests improvements
@@ -139,18 +155,22 @@ For independent checks, run agents in parallel:
```markdown
### Parallel Phase
+
Run simultaneously:
+
- code-reviewer (quality)
- security-reviewer (security)
- architect (design)
### Merge Results
+
Combine outputs into single report
```
## Arguments
$ARGUMENTS:
+
- `feature ` - Full feature workflow
- `bugfix ` - Bug fix workflow
- `refactor ` - Refactoring workflow
diff --git a/.cursor/commands/plan.md b/.cursor/commands/plan.md
index 8cf076e..da9456b 100644
--- a/.cursor/commands/plan.md
+++ b/.cursor/commands/plan.md
@@ -16,6 +16,7 @@ This command invokes the **planner** agent to create a comprehensive implementat
## When to Use
Use `/plan` when:
+
- Starting a new feature
- Making significant architectural changes
- 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.
If you want changes, respond with:
+
- "modify: [your changes]"
- "different approach: [alternative]"
- "skip phase 2 and do phase 3 first"
@@ -89,6 +91,7 @@ If you want changes, respond with:
## Integration with Other Commands
After planning:
+
- Use `/tdd` to implement with test-driven development
- Use `/build-and-fix` if build errors occur
- Use `/code-review` to review completed implementation
diff --git a/.cursor/commands/refactor-clean.md b/.cursor/commands/refactor-clean.md
index 6f5e250..5b104a5 100644
--- a/.cursor/commands/refactor-clean.md
+++ b/.cursor/commands/refactor-clean.md
@@ -3,6 +3,7 @@
Safely identify and remove dead code with test verification:
1. Run dead code analysis tools:
+
- knip: Find unused exports and files
- depcheck: Find unused dependencies
- 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
3. Categorize findings by severity:
+
- SAFE: Test files, unused utilities
- CAUTION: API routes, components
- DANGER: Config files, main entry points
@@ -17,6 +19,7 @@ Safely identify and remove dead code with test verification:
4. Propose safe deletions only
5. Before each deletion:
+
- Run full test suite
- Verify tests pass
- Apply change
diff --git a/.cursor/commands/setup-pm.md b/.cursor/commands/setup-pm.md
index fc40f01..1ca4e3a 100644
--- a/.cursor/commands/setup-pm.md
+++ b/.cursor/commands/setup-pm.md
@@ -37,6 +37,7 @@ When determining which package manager to use, the following order is checked:
## Configuration Files
### Global Configuration
+
```json
// ~/.cursor/package-manager.json
{
@@ -45,6 +46,7 @@ When determining which package manager to use, the following order is checked:
```
### Project Configuration
+
```json
// .cursor/package-manager.json
{
@@ -53,6 +55,7 @@ When determining which package manager to use, the following order is checked:
```
### package.json
+
```json
{
"packageManager": "pnpm@8.6.0"
diff --git a/.cursor/commands/tdd.md b/.cursor/commands/tdd.md
index 8bb8a66..c645074 100644
--- a/.cursor/commands/tdd.md
+++ b/.cursor/commands/tdd.md
@@ -34,12 +34,12 @@ User: /tdd Add validation for the full-page name field
```typescript
// full-page.validator.test.ts
-import { createFullPageSchema } from './full-page.validator'
+import { createFullPageSchema } from './full-page.validator';
it('rejects an empty name', () => {
- const schema = createFullPageSchema((k) => k)
- expect(schema.safeParse({ code: 'ABC', name: '' }).success).toBe(false)
-})
+ const schema = createFullPageSchema((k) => k);
+ expect(schema.safeParse({ code: 'ABC', name: '' }).success).toBe(false);
+});
```
```bash
diff --git a/.cursor/commands/test-coverage.md b/.cursor/commands/test-coverage.md
index 2d91d48..df3b26c 100644
--- a/.cursor/commands/test-coverage.md
+++ b/.cursor/commands/test-coverage.md
@@ -7,6 +7,7 @@ Analyze Vitest coverage and add missing tests:
2. Identify files below 80%
3. For each under-covered file:
+
- Unit tests for validators, transformers, utils, stores
- Testing Library tests for `@repo/ui` components
- 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
Focus on:
+
- Happy path
- Error handling
- Edge cases (null, undefined, empty)
diff --git a/.cursor/commands/update-docs.md b/.cursor/commands/update-docs.md
index 79863ed..eb9dd00 100644
--- a/.cursor/commands/update-docs.md
+++ b/.cursor/commands/update-docs.md
@@ -3,12 +3,15 @@
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
+
- 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`
+
- Document each `VITE_*` var (they are public to the client)
3. Update `apps/docs-dev` (VitePress) and the root README
+
- Where to work (`apps/web` vs `showcase`)
- Module layout (`example/full-page`)
- Preferred `@repo/*` imports
diff --git a/.cursor/commands/verify.md b/.cursor/commands/verify.md
index 5f628b1..d8f0286 100644
--- a/.cursor/commands/verify.md
+++ b/.cursor/commands/verify.md
@@ -7,23 +7,28 @@ Run comprehensive verification on current codebase state.
Execute verification in this exact order:
1. **Build Check**
+
- Run the build command for this project
- If it fails, report errors and STOP
2. **Type Check**
+
- Run TypeScript/type checker
- Report all errors with file:line
3. **Lint Check**
+
- Run linter
- Report warnings and errors
4. **Test Suite**
+
- Run all tests
- Report pass/fail count
- Report coverage percentage
5. **Console.log Audit**
+
- Search for console.log in source files
- Report locations
@@ -53,6 +58,7 @@ If any critical issues, list them with fix suggestions.
## Arguments
$ARGUMENTS can be:
+
- `quick` - Only build + types
- `full` - All checks (default)
- `pre-commit` - Checks relevant for commits
diff --git a/.cursor/contexts/dev.md b/.cursor/contexts/dev.md
index 28b64ab..73c6fc4 100644
--- a/.cursor/contexts/dev.md
+++ b/.cursor/contexts/dev.md
@@ -4,17 +4,20 @@ Mode: Active development
Focus: Implementation, coding, building features
## Behavior
+
- Write code first, explain after
- Prefer working solutions over perfect solutions
- Run tests after changes
- Keep commits atomic
## Priorities
+
1. Get it working
2. Get it right
3. Get it clean
## Tools to favor
+
- Edit, Write for code changes
- Bash for running tests/builds
- Grep, Glob for finding code
diff --git a/.cursor/contexts/research.md b/.cursor/contexts/research.md
index a298194..2aa03d1 100644
--- a/.cursor/contexts/research.md
+++ b/.cursor/contexts/research.md
@@ -4,12 +4,14 @@ Mode: Exploration, investigation, learning
Focus: Understanding before acting
## Behavior
+
- Read widely before concluding
- Ask clarifying questions
- Document findings as you go
- Don't write code until understanding is clear
## Research Process
+
1. Understand the question
2. Explore relevant code/docs
3. Form hypothesis
@@ -17,10 +19,12 @@ Focus: Understanding before acting
5. Summarize findings
## Tools to favor
+
- Read for understanding code
- Grep, Glob for finding patterns
- WebSearch, WebFetch for external docs
- Task with Explore agent for codebase questions
## Output
+
Findings first, recommendations second
diff --git a/.cursor/contexts/review.md b/.cursor/contexts/review.md
index fce643d..9c819d9 100644
--- a/.cursor/contexts/review.md
+++ b/.cursor/contexts/review.md
@@ -4,12 +4,14 @@ Mode: PR review, code analysis
Focus: Quality, security, maintainability
## Behavior
+
- Read thoroughly before commenting
- Prioritize issues by severity (critical > high > medium > low)
- Suggest fixes, don't just point out problems
- Check for security vulnerabilities
## Review Checklist
+
- [ ] Logic errors
- [ ] Edge cases
- [ ] Error handling
@@ -19,4 +21,5 @@ Focus: Quality, security, maintainability
- [ ] Test coverage
## Output Format
+
Group findings by file, severity first
diff --git a/.gitignore b/.gitignore
index 5e0724f..1f484b1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@
# Dependencies
node_modules
.pnpm-store
+.cursor/sessions/*
# Turborepo & Cache
.turbo
diff --git a/apps/docs-dev/src/apps/desktop/AUTO_UPDATER.md b/apps/docs-dev/src/apps/desktop/AUTO_UPDATER.md
index a7ee902..9a605e4 100644
--- a/apps/docs-dev/src/apps/desktop/AUTO_UPDATER.md
+++ b/apps/docs-dev/src/apps/desktop/AUTO_UPDATER.md
@@ -1,4 +1,3 @@
-
# 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)
@@ -113,8 +112,7 @@ pnpm run prebuild
GH_TOKEN=your_token electron-builder --publish always --config electron-builder.yml
```
-> [!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.
+> [!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.
### Automated Release (GitHub Actions)
@@ -232,8 +230,7 @@ publish:
Your server must host the same directory structure as the S3 layout above.
-> [!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.
+> [!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.
**Nginx reference:**
@@ -260,8 +257,7 @@ server {
## 🛡️ Code Signing: The Trust Boundary
-> [!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**.
+> [!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**.
### macOS
diff --git a/apps/docs-dev/src/apps/desktop/CONFIGURATION.md b/apps/docs-dev/src/apps/desktop/CONFIGURATION.md
index 2bb648a..e70bd20 100644
--- a/apps/docs-dev/src/apps/desktop/CONFIGURATION.md
+++ b/apps/docs-dev/src/apps/desktop/CONFIGURATION.md
@@ -1,4 +1,3 @@
-
# 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/)
diff --git a/apps/docs-dev/src/apps/desktop/IPC_ARCHITECTURE.md b/apps/docs-dev/src/apps/desktop/IPC_ARCHITECTURE.md
index b857148..623626d 100644
--- a/apps/docs-dev/src/apps/desktop/IPC_ARCHITECTURE.md
+++ b/apps/docs-dev/src/apps/desktop/IPC_ARCHITECTURE.md
@@ -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.
-
> **Scope**: [Electron](https://www.electronjs.org/) Main ↔ Renderer process communication
>
> **Enforcement Level**: Mandatory — deviations constitute security violations
@@ -17,10 +16,10 @@ This document defines the **hardened security perimeter** and communication topo
The architecture operates on three invariants:
-| Invariant | Guarantee |
-|---|---|
-| **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. |
+| Invariant | Guarantee |
+| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **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. |
| **Deterministic Lifecycle** | All IPC subscriptions are paired with unsubscribe functions, tying native event listeners to React's component lifecycle to prevent memory leaks. |
---
@@ -100,12 +99,12 @@ The Preload Script functions as a **Secure Gateway** that performs **Interface N
These settings are declared in `BrowserWindow.webPreferences` and are **non-negotiable**:
-| 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. |
-| `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. |
-| `webSecurity` | `true` | The same-origin policy is **strictly enforced**, preventing cross-origin data exfiltration from the renderer. |
+| 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. |
+| `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. |
+| `webSecurity` | `true` | The same-origin policy is **strictly enforced**, preventing cross-origin data exfiltration from the renderer. |
---
@@ -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.
-> [!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).
+> [!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).
### Step 1: Register the Handler — Main Process
diff --git a/apps/docs-dev/src/apps/desktop/index.md b/apps/docs-dev/src/apps/desktop/index.md
index c976948..b8de77c 100644
--- a/apps/docs-dev/src/apps/desktop/index.md
+++ b/apps/docs-dev/src/apps/desktop/index.md
@@ -1,4 +1,5 @@
# 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)
>
> **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.
@@ -31,10 +32,10 @@ pnpm package:desktop
## How It Works
-| 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`. |
-| **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. |
+| 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`. |
+| **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. |
---
@@ -71,33 +72,32 @@ apps/desktop/
### Development & Build
-| Script | Description |
-|---|---|
-| `pnpm dev` | Launch the electron-vite development server with live reload |
-| `pnpm build` | Compile main, preload, and renderer TypeScript modules → `out/` |
+| Script | Description |
+| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `pnpm dev` | Launch the electron-vite development server with live reload |
+| `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//dist/` → `web-dist/`. Invoked automatically before `pnpm build`. |
-| `pnpm preview` | Preview the compiled Electron app locally without generating a distributable |
+| `pnpm preview` | Preview the compiled Electron app locally without generating a distributable |
### 🚀 Packaging & Distribution
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 |
-|---|---|---|
-| `pnpm package` | Current OS | Detects host OS and builds accordingly |
-| `pnpm package:mac` | macOS | `.dmg` and `.zip` (supports x64 & arm64) |
-| `pnpm package:win` | Windows | `.exe` (NSIS Installer) |
-| `pnpm package:linux` | Linux | `.AppImage` |
+| Command | Platform | Output Artifact |
+| -------------------- | ---------- | ---------------------------------------- |
+| `pnpm package` | Current OS | Detects host OS and builds accordingly |
+| `pnpm package:mac` | macOS | `.dmg` and `.zip` (supports x64 & arm64) |
+| `pnpm package:win` | Windows | `.exe` (NSIS Installer) |
+| `pnpm package:linux` | Linux | `.AppImage` |
All artifacts are emitted to the `release/` directory.
-> [!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`.
+> [!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`.
>
> **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]
-> **macOS Code Signing**: Distributable macOS builds with Auto-Update capability **require** an Apple Developer Certificate. Provide the following environment variables:
+> [!WARNING] > **macOS Code Signing**: Distributable macOS builds with Auto-Update capability **require** an Apple Developer Certificate. Provide the following environment variables:
+>
> ```bash
> CSC_LINK=
> CSC_KEY_PASSWORD=
@@ -105,10 +105,11 @@ All artifacts are emitted to the `release/` directory.
> APPLE_APP_SPECIFIC_PASSWORD=
> APPLE_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.
-> [!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:
+> [!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:
+>
> ```yaml
> strategy:
> matrix:
@@ -162,12 +163,12 @@ 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.
-| Setting | Value | Purpose |
-|---|---|---|
-| `contextIsolation` | `true` | Preload executes in a hermetically sealed JavaScript context |
-| `nodeIntegration` | `false` | Zero Node.js API surface exposed to the renderer |
-| `sandbox` | `true` | Chromium OS-level sandbox enforced |
-| `webSecurity` | `true` | Same-origin policy strictly upheld |
+| Setting | Value | Purpose |
+| ------------------ | ------- | ------------------------------------------------------------ |
+| `contextIsolation` | `true` | Preload executes in a hermetically sealed JavaScript context |
+| `nodeIntegration` | `false` | Zero Node.js API surface exposed to the renderer |
+| `sandbox` | `true` | Chromium OS-level sandbox enforced |
+| `webSecurity` | `true` | Same-origin policy strictly upheld |
**Defense-in-depth protections in `src/main/index.ts`**:
@@ -182,8 +183,8 @@ See [IPC_ARCHITECTURE.md](./IPC_ARCHITECTURE.md) for the full security model, th
## Documentation
-| Document | Scope |
-|---|---|
-| [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 |
+| Document | Scope |
+| -------------------------------------------- | ------------------------------------------------------------------------------------- |
+| [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 |
| [IPC_ARCHITECTURE.md](./IPC_ARCHITECTURE.md) | Security model, Three-Step Bridge pattern, existing IPC channels, extensibility guide |
diff --git a/apps/docs-dev/src/index.md b/apps/docs-dev/src/index.md
index fbb548b..fa968d1 100644
--- a/apps/docs-dev/src/index.md
+++ b/apps/docs-dev/src/index.md
@@ -2,9 +2,9 @@
layout: home
hero:
- name: "Frontend Architecture"
- text: "Enterprise Monorepo"
- tagline: "A scalable, standardized foundation for Web & Desktop applications. Built for performance, consistency, and velocity."
+ name: 'Frontend Architecture'
+ text: 'Enterprise Monorepo'
+ tagline: 'A scalable, standardized foundation for Web & Desktop applications. Built for performance, consistency, and velocity.'
actions:
- theme: brand
text: Get Started
@@ -31,6 +31,7 @@ features:
link: /packages/core-api/
linkText: Explore Core
---
+
@@ -262,4 +263,4 @@ features:
padding: 24px;
}
}
-
\ No newline at end of file
+
diff --git a/apps/docs-dev/src/overview.md b/apps/docs-dev/src/overview.md
index 0c7b0bd..fb4866c 100644
--- a/apps/docs-dev/src/overview.md
+++ b/apps/docs-dev/src/overview.md
@@ -34,140 +34,155 @@ The monorepo is organized into **Apps** (deployable applications) and **Packages
## 📦 Packages Overview
### 1. `apps/web`
+
The main consumer-facing application.
-* Imports business logic from `@repo/utils`
-* Uses shared UI components from `@repo/ui`
+- Imports business logic from `@repo/utils`
+- Uses shared UI components from `@repo/ui`
**Tech Stack**:
-* [React](https://react.dev/)
-* [Vite](https://vite.dev/)
-* [TypeScript](https://www.typescriptlang.org/)
-* [Tailwind CSS](https://tailwindcss.com/)
+- [React](https://react.dev/)
+- [Vite](https://vite.dev/)
+- [TypeScript](https://www.typescriptlang.org/)
+- [Tailwind CSS](https://tailwindcss.com/)
### 2. `apps/desktop`
+
The **Electron desktop wrapper** that embeds `apps/web` for native desktop experiences.
-* In **development**: loads the Vite dev server with full hot reload
-* 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)
+- In **development**: loads the Vite dev server with full hot reload
+- 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)
**Tech Stack**:
-* [Electron](https://www.electronjs.org/) 33.x
-* [electron-vite](https://electron-vite.org/)
-* [electron-builder](https://www.electron.build/)
-* [electron-updater](https://www.npmjs.com/package/electron-updater)
+- [Electron](https://www.electronjs.org/) 33.x
+- [electron-vite](https://electron-vite.org/)
+- [electron-builder](https://www.electron.build/)
+- [electron-updater](https://www.npmjs.com/package/electron-updater)
**Key Capabilities**:
-| Feature | Description |
-|---|---|
-| 🖨️ Native Printing | Silent and direct printing via secure IPC bridge |
-| 🔄 Auto-Updates | Background downloads via GitHub Releases (switchable to S3) |
-| 🔒 Secure IPC Bridge | `contextIsolation: true`, `nodeIntegration: false`, `sandbox: true` |
-| 🌐 Custom Protocol | `app://` serves static files with SPA routing fallback to `index.html` |
-| 🛡️ CORS Bypass | Transparent Origin header rewriting for cloud API calls |
+| Feature | Description |
+| -------------------- | ---------------------------------------------------------------------- |
+| 🖨️ Native Printing | Silent and direct printing via secure IPC bridge |
+| 🔄 Auto-Updates | Background downloads via GitHub Releases (switchable to S3) |
+| 🔒 Secure IPC Bridge | `contextIsolation: true`, `nodeIntegration: false`, `sandbox: true` |
+| 🌐 Custom Protocol | `app://` serves static files with SPA routing fallback to `index.html` |
+| 🛡️ CORS Bypass | Transparent Origin header rewriting for cloud API calls |
### 3. `apps/landing`
+
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
-* 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
+- 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`
+- Locked to port **3000** (`strictPort: true`) — evacuated from the `517x` range to avoid `electron-vite` port collisions
**Tech Stack**:
-* [React](https://react.dev/)
-* [Vite](https://vite.dev/)
-* [TypeScript](https://www.typescriptlang.org/)
-* [Tailwind CSS](https://tailwindcss.com/) v4
+- [React](https://react.dev/)
+- [Vite](https://vite.dev/)
+- [TypeScript](https://www.typescriptlang.org/)
+- [Tailwind CSS](https://tailwindcss.com/) v4
### 4. `apps/docs-dev`
+
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
-* Built with **[VitePress](https://vitepress.dev/)**
+
+- Ensures components in `@repo/ui` are built and tested independently
+- Acts as a living design system and playground
+- Built with **[VitePress](https://vitepress.dev/)**
### 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.
-* Consumed by `apps/web`, `apps/landing`, and any future workspace
-* Centralizes all `@grafana/faro-*` and `@opentelemetry/*` dependencies
-* Provides plug-and-play telemetry via `initTelemetry()` + `faroAdapter`
+- Consumed by `apps/web`, `apps/landing`, and any future workspace
+- Centralizes all `@grafana/faro-*` and `@opentelemetry/*` dependencies
+- Provides plug-and-play telemetry via `initTelemetry()` + `faroAdapter`
**Tech Stack**:
-* [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)
-* [OpenTelemetry](https://opentelemetry.io/) (custom spans, distributed tracing)
-* [TypeScript](https://www.typescriptlang.org/) (strict types, module augmentation)
+- [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)
+- [OpenTelemetry](https://opentelemetry.io/) (custom spans, distributed tracing)
+- [TypeScript](https://www.typescriptlang.org/) (strict types, module augmentation)
**Key Capabilities**:
-| Feature | Description |
-|---|---|
-| 🏭 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` |
-| 🎯 Custom Spans (Opt-In) | `telemetryContext.customSpanName` creates explicit OTel spans visible in Grafana Tempo |
-| 🛡️ Error Normalization | `ApiError.fromAxiosError()` — structured, serializable error codes for all failure modes |
-| 📦 Data Services Engine | `CommonRemoteDataServices` — full CRUD + lifecycle operations with zero boilerplate |
+| Feature | Description |
+| ------------------------ | ---------------------------------------------------------------------------------------- |
+| 🏭 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` |
+| 🎯 Custom Spans (Opt-In) | `telemetryContext.customSpanName` creates explicit OTel spans visible in Grafana Tempo |
+| 🛡️ Error Normalization | `ApiError.fromAxiosError()` — structured, serializable error codes for all failure modes |
+| 📦 Data Services Engine | `CommonRemoteDataServices` — full CRUD + lifecycle operations with zero boilerplate |
### 6. `packages/core-storage`
+
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`.
### 7. `packages/core-i18n`
+
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.
**Key Capabilities**:
-| Feature | Description |
-|---|---|
-| 🌐 Hybrid Namespaces | Centralized `common` corpus + lazy-loaded feature dictionaries. |
-| 🛡️ 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. |
-| 🏢 Tenant Overrides | `applyTenantOverrides` performs a partial deep-merge to selectively override terminology. |
+| Feature | Description |
+| -------------------- | ----------------------------------------------------------------------------------------- |
+| 🌐 Hybrid Namespaces | Centralized `common` corpus + lazy-loaded feature dictionaries. |
+| 🛡️ 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. |
+| 🏢 Tenant Overrides | `applyTenantOverrides` performs a partial deep-merge to selectively override terminology. |
### 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.
**Key Capabilities**:
-| Feature | Description |
-|---|---|
-| 🧩 Zero Coupling | Publishers and subscribers interact via blind events, eliminating direct module imports and circular dependencies. |
+| Feature | Description |
+| ---------------------- | ----------------------------------------------------------------------------------------------------------------------- |
+| 🧩 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. |
-| 🧹 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. |
+| 🧹 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. |
### 9. `packages/utils`
+
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.
### 10. `packages/ui`
+
Shared UI component library (Buttons, Inputs, Cards, Layouts) with a comprehensive **Form UI Library**.
-* Ensures consistent design across all applications
-* 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
+- Ensures consistent design across all applications
+- 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
### 11. `packages/configs`
+
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
### Turborepo Caching
+
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):
+
```bash
rm -rf node_modules **/*/node_modules .turbo **/*/.turbo dist **/*/dist out **/*/out web-dist **/*/web-dist release **/*/release
```
diff --git a/apps/docs-dev/src/packages/core-api/index.md b/apps/docs-dev/src/packages/core-api/index.md
index 2590665..98b7e9c 100644
--- a/apps/docs-dev/src/packages/core-api/index.md
+++ b/apps/docs-dev/src/packages/core-api/index.md
@@ -11,6 +11,7 @@ The platform-agnostic API engine for the monorepo. Provides an isolated HTTP cli
---
## Architecture Overview
+
```mermaid
graph TD
%% ─── Styling Definitions (Dark-Mode Friendly Enterprise Palette) ───
@@ -46,7 +47,7 @@ graph TD
%% ─── Flow & Relationships ───
WEB & LAND & DESK ===>|instantiates| FACTORY
WEB & LAND & DESK ===>|extends| COMMON
-
+
COMMON --->|executes via| FACTORY
FACTORY -.->|reports via| FARO
FACTORY -.->|throws| API_ERR
@@ -61,13 +62,13 @@ graph TD
%% ─── Subgraph Backgrounds (Transparent for Native GitHub Support) ───
style Apps fill:transparent,stroke:#3b82f6,stroke-width:2px,stroke-dasharray: 5 5
style Core fill:transparent,stroke:#94a3b8,stroke-width:2px,stroke-dasharray: 5 5
-
+
%% Nested subgraphs also need transparent backgrounds to prevent glaring white boxes in dark mode
style HTTP fill:transparent,stroke:#cbd5e1,stroke-width:1px,stroke-dasharray: 5 5
style OBS fill:transparent,stroke:#cbd5e1,stroke-width:1px,stroke-dasharray: 5 5
style DATA fill:transparent,stroke:#cbd5e1,stroke-width:1px,stroke-dasharray: 5 5
style ERRORS fill:transparent,stroke:#cbd5e1,stroke-width:1px,stroke-dasharray: 5 5
- ```
+```
### Data Flow Lifecycle
@@ -102,7 +103,7 @@ sequenceDiagram
H->>F: onRequestStart() (Log + Span)
H->>A: hooks.onRequest() (Inject Token)
A->>N: fetch/XHR
-
+
alt Success (2xx)
N-->>A: return Response
A->>F: onRequestEnd() (Close Span)
@@ -156,20 +157,20 @@ export const apiClient = createHttpClient(
### Configuration
-| Property | Type | Default | Description |
-|---|---|---|---|
-| `baseURL` | `string` | *required* | Base URL for all requests |
-| `timeout` | `number` | `15000` | Default request timeout (ms) |
-| `defaultHeaders` | `Record` | `{}` | Headers applied to every request |
-| `observability` | `IObservabilityAdapter` | `noopAdapter` | Observability adapter (Faro or no-op) |
+| Property | Type | Default | Description |
+| ---------------- | ------------------------ | ------------- | ------------------------------------- |
+| `baseURL` | `string` | _required_ | Base URL for all requests |
+| `timeout` | `number` | `15000` | Default request timeout (ms) |
+| `defaultHeaders` | `Record` | `{}` | Headers applied to every request |
+| `observability` | `IObservabilityAdapter` | `noopAdapter` | Observability adapter (Faro or no-op) |
### Interceptor Hooks
-| Hook | Signature | Purpose |
-|---|---|---|
-| `onRequest` | `(config) => config` | Inject auth tokens, tenant headers |
-| `onResponse` | `(response) => response` | Transform response shapes |
-| `onResponseError` | `(error) => never` | App-specific error handling (e.g., 401 redirect) |
+| Hook | Signature | Purpose |
+| ----------------- | ------------------------ | ------------------------------------------------ |
+| `onRequest` | `(config) => config` | Inject auth tokens, tenant headers |
+| `onResponse` | `(response) => response` | Transform response shapes |
+| `onResponseError` | `(error) => never` | App-specific error handling (e.g., 401 redirect) |
---
@@ -179,13 +180,12 @@ export const apiClient = createHttpClient(
The observability layer operates in two complementary modes:
-| 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 |
-| **Custom Span** (opt-in) | Via `telemetryContext.customSpanName` | Creates an explicit OTel span with custom tags, visible in [Grafana Tempo](https://grafana.com/oss/tempo/) |
+| 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 |
+| **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]
-> `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.
+> [!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.
### Initialization
@@ -206,34 +206,34 @@ initTelemetry({
### `TelemetryConfig`
-| Property | Type | Required | Description |
-|---|---|---|---|
-| `appName` | `string` | ✅ | Application name for Faro + OTel resource attributes |
-| `appVersion` | `string` | ✅ | SemVer version |
-| `telemetryUrl` | `string` | ✅ | Grafana Faro collector URL |
-| `environment` | `string` | ✅ | Deployment environment (`production`, `staging`, `development`) |
-| `otlpTraceUrl` | `string` | — | Separate OTLP trace endpoint for direct Tempo ingestion |
-| `propagateTraceHeaderCorsUrls` | `Array` | — | CORS patterns for W3C trace context propagation (default: `[/.*/]`) |
+| Property | Type | Required | Description |
+| ------------------------------ | ------------------------- | -------- | ------------------------------------------------------------------- |
+| `appName` | `string` | ✅ | Application name for Faro + OTel resource attributes |
+| `appVersion` | `string` | ✅ | SemVer version |
+| `telemetryUrl` | `string` | ✅ | Grafana Faro collector URL |
+| `environment` | `string` | ✅ | Deployment environment (`production`, `staging`, `development`) |
+| `otlpTraceUrl` | `string` | — | Separate OTLP trace endpoint for direct Tempo ingestion |
+| `propagateTraceHeaderCorsUrls` | `Array` | — | CORS patterns for W3C trace context propagation (default: `[/.*/]`) |
### Audit Headers
Every request dispatched through `BaseRemoteDataServices` automatically attaches two business audit headers:
-| Header | Source | Purpose |
-|---|---|---|
-| `ex-module-key` | `DataServicesConfig.moduleKey` | Identifies the business module (e.g., `BOOKING`) |
-| `ex-module-action` | `RequestDescriptor.action` | Identifies the operation (e.g., `READ`, `CREATE`) |
+| Header | Source | Purpose |
+| ------------------ | ------------------------------ | ------------------------------------------------- |
+| `ex-module-key` | `DataServicesConfig.moduleKey` | Identifies the business module (e.g., `BOOKING`) |
+| `ex-module-action` | `RequestDescriptor.action` | Identifies the operation (e.g., `READ`, `CREATE`) |
These headers are extracted by the `faroAdapter` and included in all Faro `pushLog`, `pushError`, and `pushEvent` calls as top-level context — making them directly queryable in **LogQL ([Loki](https://grafana.com/oss/loki/))**.
### Span Safety Guarantees
-| Guarantee | Mechanism |
-|---|---|
-| **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 error swallowing** | All adapter calls are wrapped in try-catch in `create-http-client.ts` |
-| **No crash on timeout** | `null`/`undefined` config guards on all `error.config` access |
+| Guarantee | Mechanism |
+| ---------------------------- | ----------------------------------------------------------------------------- |
+| **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 error swallowing** | All adapter calls are wrapped in try-catch in `create-http-client.ts` |
+| **No crash on timeout** | `null`/`undefined` config guards on all `error.config` access |
---
@@ -254,32 +254,29 @@ interface BookingEntity extends BaseEntity {
status: 'pending' | 'confirmed' | 'cancelled';
}
-export const bookingServices = new CommonRemoteDataServices(
- apiClient,
- {
- apiUrl: '/bookings',
- moduleKey: 'BOOKING',
- },
-);
+export const bookingServices = new CommonRemoteDataServices(apiClient, {
+ apiUrl: '/bookings',
+ moduleKey: 'BOOKING',
+});
```
### Available Operations
-| Method | HTTP | URL Template | Description |
-|---|---|---|---|
-| `getMany(config?)` | GET | `/bookings` | Fetch paginated list |
-| `getOne(id, config?)` | GET | `/bookings/:id` | Fetch single entity |
-| `create(data, config?)` | POST | `/bookings` | Create new entity |
-| `edit(id, data, config?)` | PUT | `/bookings/:id` | Update entity |
-| `delete(id, config?)` | DELETE | `/bookings/:id` | Delete entity |
-| `batchDelete(ids, config?)` | DELETE | `/bookings/batch` | Delete multiple |
-| `activate(id)` | PATCH | `/bookings/:id/activate` | Activate entity |
-| `deactivate(id)` | PATCH | `/bookings/:id/deactivate` | Deactivate entity |
-| `confirmProcessData(id)` | PATCH | `/bookings/:id/confirm-process-data` | Confirm data processing |
-| `confirmProcessTransaction(id)` | PATCH | `/bookings/:id/confirm-process-transaction` | Confirm transaction |
-| `cancelProcessTransaction(id)` | PATCH | `/bookings/:id/cancel-process-transaction` | Cancel transaction |
-| `rollbackProcessTransaction(id)` | PATCH | `/bookings/:id/rollback-process-transaction` | Rollback transaction |
-| `holdProcessTransaction(id)` | PATCH | `/bookings/:id/hold-process-transaction` | Hold transaction |
+| Method | HTTP | URL Template | Description |
+| -------------------------------- | ------ | -------------------------------------------- | ----------------------- |
+| `getMany(config?)` | GET | `/bookings` | Fetch paginated list |
+| `getOne(id, config?)` | GET | `/bookings/:id` | Fetch single entity |
+| `create(data, config?)` | POST | `/bookings` | Create new entity |
+| `edit(id, data, config?)` | PUT | `/bookings/:id` | Update entity |
+| `delete(id, config?)` | DELETE | `/bookings/:id` | Delete entity |
+| `batchDelete(ids, config?)` | DELETE | `/bookings/batch` | Delete multiple |
+| `activate(id)` | PATCH | `/bookings/:id/activate` | Activate entity |
+| `deactivate(id)` | PATCH | `/bookings/:id/deactivate` | Deactivate entity |
+| `confirmProcessData(id)` | PATCH | `/bookings/:id/confirm-process-data` | Confirm data processing |
+| `confirmProcessTransaction(id)` | PATCH | `/bookings/:id/confirm-process-transaction` | Confirm transaction |
+| `cancelProcessTransaction(id)` | PATCH | `/bookings/:id/cancel-process-transaction` | Cancel transaction |
+| `rollbackProcessTransaction(id)` | PATCH | `/bookings/:id/rollback-process-transaction` | Rollback transaction |
+| `holdProcessTransaction(id)` | PATCH | `/bookings/:id/hold-process-transaction` | Hold transaction |
All batch variants (`batchActivate`, `batchDeactivate`, etc.) are also available.
@@ -308,8 +305,11 @@ import { initTelemetry } from '@repo/core-api/observability/setup';
initTelemetry({
appName: import.meta.env.VITE_APP_NAME || 'fe-monorepo-web',
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)',
- otlpTraceUrl: import.meta.env.VITE_OTLP_TRACE_URL || '[https://telemetry.eigen.co.id/v1/traces](https://telemetry.eigen.co.id/v1/traces)',
+ telemetryUrl:
+ 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',
});
@@ -345,10 +345,10 @@ export interface BookingEntity extends BaseEntity {
totalAmount: number;
}
-export const bookingServices = new CommonRemoteDataServices(
- apiClient,
- { apiUrl: '/bookings', moduleKey: 'BOOKING' },
-);
+export const bookingServices = new CommonRemoteDataServices(apiClient, {
+ apiUrl: '/bookings',
+ moduleKey: 'BOOKING',
+});
```
### 4. Consume in a React Component
@@ -425,11 +425,11 @@ await bookingServices.getMany({
### What Happens at Each Stage
-| 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 Success** | — | Closes span (OK). If `pushEventOnSuccess`, pushes Faro event |
-| **Request Error** | Faro `pushError` + `pushLog` (ERROR) | + Closes span (ERROR), records exception |
+| 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 Success** | — | Closes span (OK). If `pushEventOnSuccess`, pushes Faro event |
+| **Request Error** | Faro `pushError` + `pushLog` (ERROR) | + Closes span (ERROR), records exception |
---
@@ -444,10 +444,10 @@ try {
await bookingServices.getOne('42');
} catch (err) {
if (err instanceof ApiError) {
- err.code; // ApiErrorCode.NOT_FOUND
- err.status; // 404
+ err.code; // ApiErrorCode.NOT_FOUND
+ err.status; // 404
err.message; // "Booking not found"
- err.data; // Raw server response body
+ err.data; // Raw server response body
err.toJSON(); // Serializable for logging
}
}
@@ -455,25 +455,25 @@ try {
### Error Codes
-| Code | HTTP Status | Description |
-|---|---|---|
-| `BAD_REQUEST` | 400 | Invalid request parameters |
-| `UNAUTHORIZED` | 401 | Missing or expired token |
-| `FORBIDDEN` | 403 | Insufficient permissions |
-| `NOT_FOUND` | 404 | Resource not found |
-| `TIMEOUT` | — | Request timed out (`ECONNABORTED`) |
-| `CANCELLED` | — | Request was cancelled (`ERR_CANCELED`) |
-| `NETWORK_ERROR` | — | No response received |
-| `SERVER_ERROR` | 500+ | Internal server error |
+| Code | HTTP Status | Description |
+| --------------- | ----------- | -------------------------------------- |
+| `BAD_REQUEST` | 400 | Invalid request parameters |
+| `UNAUTHORIZED` | 401 | Missing or expired token |
+| `FORBIDDEN` | 403 | Insufficient permissions |
+| `NOT_FOUND` | 404 | Resource not found |
+| `TIMEOUT` | — | Request timed out (`ECONNABORTED`) |
+| `CANCELLED` | — | Request was cancelled (`ERR_CANCELED`) |
+| `NETWORK_ERROR` | — | No response received |
+| `SERVER_ERROR` | 500+ | Internal server error |
---
## Package Exports
-| Import Path | Contents |
-|---|---|
-| `@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/setup` | `initTelemetry`, `getFaro`, `TelemetryConfig` |
-| `@repo/core-api/data-services` | `BaseRemoteDataServices`, `CommonRemoteDataServices`, types, constants |
-| `@repo/core-api/errors` | `ApiError`, `ApiErrorCode` |
\ No newline at end of file
+| Import Path | Contents |
+| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------- |
+| `@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/setup` | `initTelemetry`, `getFaro`, `TelemetryConfig` |
+| `@repo/core-api/data-services` | `BaseRemoteDataServices`, `CommonRemoteDataServices`, types, constants |
+| `@repo/core-api/errors` | `ApiError`, `ApiErrorCode` |
diff --git a/apps/docs-dev/src/packages/core-api/transformers.md b/apps/docs-dev/src/packages/core-api/transformers.md
index 9a1b1a7..4d0a95e 100644
--- a/apps/docs-dev/src/packages/core-api/transformers.md
+++ b/apps/docs-dev/src/packages/core-api/transformers.md
@@ -10,13 +10,13 @@
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) |
-| ------------------------------ | ---------------------------- |
-| `snake_case` field names | `camelCase` field names |
-| Deeply nested structures | Flattened/normalized shapes |
-| Raw ISO date strings | Parsed `Date` objects |
-| No computed fields | Derived/computed properties |
-| Backend-specific enums | Frontend-friendly enums |
+| API (DTO) | Frontend (Entity) |
+| ------------------------ | --------------------------- |
+| `snake_case` field names | `camelCase` field names |
+| Deeply nested structures | Flattened/normalized shapes |
+| Raw ISO date strings | Parsed `Date` objects |
+| No computed fields | Derived/computed properties |
+| Backend-specific enums | Frontend-friendly enums |
Without transformers, this mapping logic leaks into components, hooks, and services — violating the **Single Responsibility Principle** and making the codebase harder to test and maintain.
@@ -54,6 +54,7 @@ graph LR
```
**Data flows:**
+
- **API → Frontend:** Response DTO → `transformToEntity()` → Domain Entity
- **Frontend → API:** Domain Entity → `transformToDTO()` → Request DTO
@@ -153,14 +154,14 @@ interface IDataTransformer {
Abstract class implementing `IDataTransformer` with sensible defaults.
-| Method | Default Behavior | Override When |
-| ------------------------- | ---------------------------------------- | ------------------------------------------ |
-| `transformToEntity` | 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 |
-| `transformGetManyResponse`| Maps each item via `transformToEntity` | List responses need bulk transformations |
-| `transformCreatePayload` | Delegates to `transformToDTO` | Create payloads need special handling (e.g., strip IDs) |
-| `transformEditPayload` | Delegates to `transformToDTO` | Edit payloads differ from create |
+| Method | Default Behavior | Override When |
+| -------------------------- | -------------------------------------- | ------------------------------------------------------- |
+| `transformToEntity` | 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 |
+| `transformGetManyResponse` | Maps each item via `transformToEntity` | List responses need bulk transformations |
+| `transformCreatePayload` | Delegates to `transformToDTO` | Create payloads need special handling (e.g., strip IDs) |
+| `transformEditPayload` | Delegates to `transformToDTO` | Edit payloads differ from create |
---
@@ -168,14 +169,14 @@ Abstract class implementing `IDataTransformer` with sensible defaults.
When a transformer is injected via `DataServicesConfig.transformer`, the base service methods automatically apply transformations:
-| Service Method | Transformer Hook Used | Direction |
-| -------------- | -------------------------------- | --------------- |
-| `getOne()` | `transformGetOneResponse()` | Response → Entity |
-| `getMany()` | `transformGetManyResponse()` | Response → Entity |
-| `create()` | `transformCreatePayload()` | Entity → DTO |
-| `edit()` | `transformEditPayload()` | Entity → DTO |
-| `delete()` | None (no data transformation) | — |
-| `customRequest()` | None (manual transformation) | — |
+| Service Method | Transformer Hook Used | Direction |
+| ----------------- | ----------------------------- | ----------------- |
+| `getOne()` | `transformGetOneResponse()` | Response → Entity |
+| `getMany()` | `transformGetManyResponse()` | Response → Entity |
+| `create()` | `transformCreatePayload()` | Entity → DTO |
+| `edit()` | `transformEditPayload()` | Entity → DTO |
+| `delete()` | None (no data transformation) | — |
+| `customRequest()` | None (manual transformation) | — |
> **Important:** If no transformer is injected, all methods behave exactly as before — data passes through unchanged. This ensures 100% backward compatibility.
@@ -277,8 +278,12 @@ Adding transformers to existing services requires **zero breaking changes**:
```typescript
class MyTransformer extends BaseDataTransformer {
- transformToEntity(dto: MyDTO): MyEntity { /* ... */ }
- transformToDTO(entity: MyEntity): MyDTO { /* ... */ }
+ transformToEntity(dto: MyDTO): MyEntity {
+ /* ... */
+ }
+ transformToDTO(entity: MyEntity): MyDTO {
+ /* ... */
+ }
}
```
@@ -296,8 +301,12 @@ class MyTransformer extends BaseDataTransformer {
```typescript
class MyTransformer extends BaseDataTransformer {
- transformToEntity(dto: MyDTO): MyEntity { /* ... */ }
- transformToDTO(entity: MyEntity): MyDTO { /* ... */ }
+ transformToEntity(dto: MyDTO): MyEntity {
+ /* ... */
+ }
+ transformToDTO(entity: MyEntity): MyDTO {
+ /* ... */
+ }
// Only override if getOne needs special handling
override transformGetOneResponse(dto: MyDTO): MyEntity {
@@ -315,9 +324,9 @@ class MyTransformer extends BaseDataTransformer {
A full working example is available in the showcase booking feature:
-| File | Description |
-| ---- | ----------- |
-| `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/advanced-booking.transformer.ts` | Extended transformer with custom chart method |
-| `apps/showcase/.../booking/data/advanced-booking.data-services.ts` | Extended service with custom `getAvailabilityChart()` |
+| File | Description |
+| ------------------------------------------------------------------ | ------------------------------------------------------ |
+| `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/advanced-booking.transformer.ts` | Extended transformer with custom chart method |
+| `apps/showcase/.../booking/data/advanced-booking.data-services.ts` | Extended service with custom `getAvailabilityChart()` |
diff --git a/apps/docs-dev/src/packages/core-events/index.md b/apps/docs-dev/src/packages/core-events/index.md
index 881c711..452c9b9 100644
--- a/apps/docs-dev/src/packages/core-events/index.md
+++ b/apps/docs-dev/src/packages/core-events/index.md
@@ -19,6 +19,7 @@ The Global Pub/Sub & Hardware Integration Blueprint.
### Architectural Topology
### 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.
```mermaid
@@ -45,6 +46,7 @@ graph LR
```
### 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.
```mermaid
@@ -65,12 +67,12 @@ graph TD
subgraph Apps ["apps/web (App Autonomy)"]
D[[events.d.ts Declaration Merging]]
-
+
%% Publishers
A([Cashier UI])
B([Profile Settings])
C([WebSocket Client])
-
+
%% Subscribers
X([Electron IPC Bridge])
Y([IndexedDB Sync])
@@ -107,18 +109,16 @@ graph TD
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.
-* **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.
-* **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).
-
+- **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.
+- **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).
---
## Defining Events (Module Augmentation)
-> [!IMPORTANT]
-> **Do NOT add application events to `packages/core-events/src/events.registry.ts`.**
+> [!IMPORTANT] > **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 exports an open `AppEventRegistry` interface. Apps extend it using TypeScript's `declare module` syntax — the same pattern used for `@types/*` across the JS ecosystem.
@@ -148,7 +148,7 @@ declare module '@repo/core-events' {
'STORE:ORDER_PLACED': OrderPayload;
'STORE:ORDER_CANCELLED': { orderId: string; reason: string };
'UI:SIDEBAR_TOGGLED': { collapsed: boolean };
-
+
// Explicit payloads for the examples below:
'DEVICE:PRINT_RECEIPT': { receiptId: string; items: any[]; total: number; cashierName: string; timestamp: number };
'WS:STOCK_UPDATE': { id: string; price: number };
@@ -180,12 +180,12 @@ function OrderTracker() {
### Why this pattern?
-| Concern | Old (Hardcoded) | New (Module Augmentation) |
-|---|---|---|
-| 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 |
-| Multiple apps share the same registry? | ❌ Collision risk | ✅ Each app has its own `.d.ts` |
-| Type safety / autocomplete | ✅ Works | ✅ Works identically |
+| Concern | Old (Hardcoded) | New (Module Augmentation) |
+| -------------------------------------- | --------------------- | ------------------------------------ |
+| 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 |
+| Multiple apps share the same registry? | ❌ Collision risk | ✅ Each app has its own `.d.ts` |
+| Type safety / autocomplete | ✅ Works | ✅ Works identically |
---
@@ -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.
**Publisher (Cashier UI)**:
+
```tsx
import { usePublishEvent } from '@repo/core-events';
@@ -234,7 +235,7 @@ export function CashierUI() {
publish('DEVICE:PRINT_RECEIPT', {
receiptId: 'RCP-123',
items: [],
- total: 45.00,
+ total: 45.0,
cashierName: 'Firman',
timestamp: Date.now(),
});
@@ -245,6 +246,7 @@ export function CashierUI() {
```
**Subscriber (Headless Listener)**:
+
```tsx
import { useAppEvent } from '@repo/core-events';
@@ -274,10 +276,11 @@ 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.
**Parent Grid (Never re-renders)**:
+
```tsx
export function LiveStockGrid() {
// Generates 1000 IDs once. No stock data is stored here!
- const stockIds = generateStockIds(1000);
+ const stockIds = generateStockIds(1000);
return (
@@ -292,6 +295,7 @@ export function LiveStockGrid() {
```
**Child Row (Targeted Updates)**:
+
```tsx
import { memo, useState } from 'react';
import { useAppEvent } from '@repo/core-events';
@@ -303,7 +307,7 @@ export const StockRow = memo(function StockRow({ stockId }) {
// CRITICAL: Filter out events for other rows.
// 999 out of 1000 rows will exit here instantly without causing a re-render.
if (payload.id !== stockId) return;
-
+
// Only the targeted row updates its local state
setData(payload);
});
@@ -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.
**Publisher (Profile UI)**:
+
```tsx
import { usePublishEvent } from '@repo/core-events';
@@ -347,6 +352,7 @@ export function ProfileSettingsUI() {
```
**Subscriber (Storage Sync Listener)**:
+
```tsx
import { useAppEvent, usePublishEvent } from '@repo/core-events';
import { secureIndexedDB } from '@repo/core-storage';
@@ -355,7 +361,7 @@ export function StorageSyncListener() {
const publish = usePublishEvent();
useAppEvent('AUTH:PROFILE_UPDATED', (payload) => {
- // Automatically encrypted at rest because 'user_profile'
+ // Automatically encrypted at rest because 'user_profile'
// is defined in ENCRYPTED_KEYS in @repo/core-storage
secureIndexedDB.setItem('user_profile', payload).catch((error) => {
// Escalate to global error handler instead of swallowing it
@@ -365,4 +371,4 @@ export function StorageSyncListener() {
return null;
}
-```
\ No newline at end of file
+```
diff --git a/apps/docs-dev/src/packages/core-storage/index.md b/apps/docs-dev/src/packages/core-storage/index.md
index afd15c9..908e664 100644
--- a/apps/docs-dev/src/packages/core-storage/index.md
+++ b/apps/docs-dev/src/packages/core-storage/index.md
@@ -4,11 +4,12 @@
>
> **Description:** Enterprise storage engine providing AES-encrypted LocalStorage, strict-gatekeeper IndexedDB, and offline-first PouchDB with bi-directional CouchDB cloud synchronization.
-`@repo/core-storage` is the **Enterprise-grade, multi-tool storage engine** for the Eigen Monorepo.
+`@repo/core-storage` is the **Enterprise-grade, multi-tool storage engine** for the Eigen Monorepo.
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:
+
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)
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.
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`).
2. Highly sensitive tokens (e.g., JWTs) are automatically routed through the `@repo/utils` AES Encryption pipeline before touching the disk.
@@ -56,19 +58,19 @@ graph TD
%% 1. Initialization Flow
REG -.->|Injects Keys & Config| FAC
FAC -.->|Returns| INST
-
+
%% 2. Runtime Execution Flow
UI ===>|getItem / setItem| INST
INST ---> API
API ---> VAL
-
+
%% 3. Gatekeeper Decision Tree
VAL -.->|Invalid Key| ERR
VAL ===>|Sensitive Key| ENC
-
+
VAL --->|Plain-text Key| LOCAL
VAL --->|Plain-text Key| IDB
-
+
%% 4. Post-Encryption Storage
ENC ===>|Encrypted Data| LOCAL
ENC ===>|Encrypted Data| IDB
@@ -107,10 +109,10 @@ const theme = await appStorage.getItem('THEME'); // Plaintext on disk
### ✅ Do's and ❌ Don'ts
-* **✅ 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.
-* **❌ 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.
+- **✅ 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.
+- **❌ 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.
---
@@ -147,7 +149,7 @@ graph TD
%% ─── Flow & Relationships ───
COMP ===>|Read / Write| L_SALES
COMP ===>|Read / Write| L_INV
-
+
MGR -.->|Instantiates Multi-DB| L_SALES
MGR -.->|Instantiates Multi-DB| L_INV
@@ -178,7 +180,7 @@ export const dbManager = new PouchDBManager();
export const itemDB = dbManager.register({
localName: 'items_db',
- remoteUrl: 'http://admin:password@localhost:5984/items_db'
+ remoteUrl: 'http://admin:password@localhost:5984/items_db',
});
```
@@ -186,24 +188,24 @@ export const itemDB = dbManager.register({
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 |
-|---|---|
-| `create(data)` | Inserts a new document. Auto-generates `_id` if omitted. |
-| `update(id, data)` | Auto-fetches the latest `_rev` to merge payloads cleanly. |
-| `delete(id)` | Auto-fetches the latest `_rev` to safely remove the document. |
-| `getAll()` | Retrieves all documents (filters out internal `_design/` docs). |
-| `find(options)` | Queries using MongoDB-style selectors (via `pouchdb-find`). |
+| Method | Description |
+| ------------------ | --------------------------------------------------------------- |
+| `create(data)` | Inserts a new document. Auto-generates `_id` if omitted. |
+| `update(id, data)` | Auto-fetches the latest `_rev` to merge payloads cleanly. |
+| `delete(id)` | Auto-fetches the latest `_rev` to safely remove the document. |
+| `getAll()` | Retrieves all documents (filters out internal `_design/` docs). |
+| `find(options)` | Queries using MongoDB-style selectors (via `pouchdb-find`). |
```typescript
// Example: Querying data using selectors
const expensiveItems = await itemDB.find({
- selector: { price: { $gt: 100 }, category: 'electronics' }
+ selector: { price: { $gt: 100 }, category: 'electronics' },
});
```
### 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
import { useEffect, useCallback, useState } from 'react';
@@ -233,7 +235,7 @@ export function InventoryList() {
### 4. Envelope Pattern (`PouchEnvelopeDBManager`)
-If you want to store multiple types of entities (e.g. `items`, `bookings`, `activities`) in a single CouchDB/PouchDB database to simplify sync setup, use the **Envelope Pattern**.
+If you want to store multiple types of entities (e.g. `items`, `bookings`, `activities`) in a single CouchDB/PouchDB database to simplify sync setup, use the **Envelope Pattern**.
Instead of `PouchDBManager`, instantiate a `PouchEnvelopeDBManager`. It provides the exact same `PouchService` API (CRUD + Find), but automatically wraps documents into an envelope format internally: `{ _id: "entityName:businessId", entity: "entityName", data: { ... } }`.
@@ -244,16 +246,22 @@ import type { ItemEntity, BookingEntity } from './types';
export const envelopeDbManager = new PouchEnvelopeDBManager();
// Registers to the SAME database 'master_db', but scoped to 'item'
-export const itemDB = envelopeDbManager.register({
- localName: 'master_db',
- remoteUrl: 'http://admin:pass@localhost:5984/master_db'
-}, 'item');
+export const itemDB = envelopeDbManager.register(
+ {
+ localName: 'master_db',
+ remoteUrl: 'http://admin:pass@localhost:5984/master_db',
+ },
+ 'item',
+);
// Registers to the SAME database 'master_db', but scoped to 'booking'
-export const bookingDB = envelopeDbManager.register({
- localName: 'master_db',
- remoteUrl: 'http://admin:pass@localhost:5984/master_db'
-}, 'booking');
+export const bookingDB = envelopeDbManager.register(
+ {
+ localName: 'master_db',
+ remoteUrl: 'http://admin:pass@localhost:5984/master_db',
+ },
+ 'booking',
+);
// API usage remains identical!
await itemDB.create({ _id: '123', name: 'Widget' }); // Stored as "item:123"
@@ -265,19 +273,20 @@ const results = await itemDB.search('widget keyword', ['data.name', 'data.sku'])
### ✅ Do's and ❌ Don'ts for PouchDB
-* **✅ 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.
-* **❌ 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.
+- **✅ 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.
+- **❌ 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.
---
## ⚠️ Troubleshooting
### 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.
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.
> **DO NOT try to fix this in the frontend Vite config or proxy!**
-> This is strictly a CouchDB server policy issue. You must enable CORS directly on the CouchDB cluster (editing its `local.ini` or via its dashboard) to allow `origins`, `credentials`, and `headers`.
\ No newline at end of file
+> This is strictly a CouchDB server policy issue. You must enable CORS directly on the CouchDB cluster (editing its `local.ini` or via its dashboard) to allow `origins`, `credentials`, and `headers`.
diff --git a/apps/docs-dev/src/packages/ui/ACTION-TOOLS.md b/apps/docs-dev/src/packages/ui/ACTION-TOOLS.md
index df3c367..f6f3008 100644
--- a/apps/docs-dev/src/packages/ui/ACTION-TOOLS.md
+++ b/apps/docs-dev/src/packages/ui/ACTION-TOOLS.md
@@ -12,12 +12,7 @@ These components automatically adapt to screen sizes, handle tooltip generation,
## Import Statement
```tsx
-import {
- PageActions,
- RowActions,
- type PageAction,
- type RowAction
-} from '@repo/ui/components';
+import { PageActions, RowActions, type PageAction, type RowAction } from '@repo/ui/components';
```
## Usage Examples
@@ -52,11 +47,11 @@ function PageHeader() {
icon: ,
onClick: (k) => console.log(k),
},
- {
- key: 'print-copy',
- label: 'Print Copy',
- icon: ,
- onClick: (k) => console.log(k)
+ {
+ key: 'print-copy',
+ label: 'Print Copy',
+ icon: ,
+ onClick: (k) => console.log(k),
},
],
},
@@ -132,17 +127,17 @@ function DataTable() {
### PageActions Props
-| Prop | Type | Default | Description |
-|---|---|---|---|
-| `actions` | `PageAction[]` | Required | Array of configured page-level actions. |
-| `onClose` | `() => void` | `undefined` | Optional callback triggered when the close (X) button is clicked. |
+| Prop | Type | Default | Description |
+| --------- | -------------- | ----------- | ----------------------------------------------------------------- |
+| `actions` | `PageAction[]` | Required | Array of configured page-level actions. |
+| `onClose` | `() => void` | `undefined` | Optional callback triggered when the close (X) button is clicked. |
### RowActions Props
-| Prop | Type | Default | Description |
-|---|---|---|---|
-| `actions` | `RowAction[]` | `[]` | Array of configured row-level actions. |
-| `showLabels` | `boolean` | `false` | If true, renders the text label alongside the icon for top-level buttons. |
+| Prop | Type | Default | Description |
+| ------------ | ------------- | ------- | ------------------------------------------------------------------------- |
+| `actions` | `RowAction[]` | `[]` | Array of configured row-level actions. |
+| `showLabels` | `boolean` | `false` | If true, renders the text label alongside the icon for top-level buttons. |
### Action Definitions
@@ -150,29 +145,29 @@ Both `PageAction` and `RowAction` share a common base interface.
**Base Action Properties (`BaseAction`)**
-| Property | Type | Description |
-|---|---|---|
-| `key` | `string` | Unique identifier. Required for 'action', optional for 'divider'. |
-| `type` | `'action'` \| `'divider'` | Type of action. Defaults to 'action'. |
-| `icon` | `ReactNode` | Visual representation of the action. |
-| `disabled` | `boolean` | Disables interaction if set to true. |
-| `intent` | `'default'` \| `'success'` \| `'warning'` \| `'destructive'` \| `'primary'` | Semantic context to determine visual emphasis (color mapping). |
-| `onClick` | `(key: string) => void` | Callback triggered upon execution. |
+| Property | Type | Description |
+| ---------- | --------------------------------------------------------------------------- | ----------------------------------------------------------------- |
+| `key` | `string` | Unique identifier. Required for 'action', optional for 'divider'. |
+| `type` | `'action'` \| `'divider'` | Type of action. Defaults to 'action'. |
+| `icon` | `ReactNode` | Visual representation of the action. |
+| `disabled` | `boolean` | Disables interaction if set to true. |
+| `intent` | `'default'` \| `'success'` \| `'warning'` \| `'destructive'` \| `'primary'` | Semantic context to determine visual emphasis (color mapping). |
+| `onClick` | `(key: string) => void` | Callback triggered upon execution. |
**`PageAction` Specific Properties**
-| Property | Type | Description |
-|---|---|---|
-| `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. |
-| `children` | `PageAction[]` | Nested actions rendered as a dropdown menu below the main button. |
+| Property | Type | Description |
+| ---------- | -------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
+| `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. |
+| `children` | `PageAction[]` | Nested actions rendered as a dropdown menu below the main button. |
**`RowAction` Specific Properties**
-| Property | Type | Description |
-|---|---|---|
-| `label` | `string` | Text primarily used when rendered inside a nested menu item. |
-| `tooltip` | `string` | Optional text displayed on hover over the standalone icon. |
+| Property | Type | Description |
+| ---------- | ------------- | ------------------------------------------------------------ |
+| `label` | `string` | Text primarily used when rendered inside a nested menu item. |
+| `tooltip` | `string` | Optional text displayed on hover over the standalone icon. |
| `children` | `RowAction[]` | Nested actions that will be rendered inside a dropdown menu. |
## Best Practices
diff --git a/apps/docs-dev/src/packages/ui/CORE-APP-SHELL.md b/apps/docs-dev/src/packages/ui/CORE-APP-SHELL.md
index ca9c16b..77a3330 100644
--- a/apps/docs-dev/src/packages/ui/CORE-APP-SHELL.md
+++ b/apps/docs-dev/src/packages/ui/CORE-APP-SHELL.md
@@ -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.
-> **Package**: `@repo/ui` · **Module Path**: `@repo/ui/components`
-> **Dependencies**: React 18+, [Mantine v8](https://mantine.dev/) (`AppShell`), [`@mantine/hooks`](https://mantine.dev/hooks/package/)
+> **Package**: `@repo/ui` · **Module Path**: `@repo/ui/components` > **Dependencies**: React 18+, [Mantine v8](https://mantine.dev/) (`AppShell`), [`@mantine/hooks`](https://mantine.dev/hooks/package/)
---
@@ -100,11 +99,11 @@ interface CoreAppShellConfig {
}
```
-| Property | Type | Required | Description |
-|---|---|---|---|
-| `variant` | `LayoutVariant` | ✅ | Determines the structural layout mode |
-| `dimensions` | `CoreAppShellDimensions` | — | Override default pixel dimensions |
-| `features` | `CoreAppShellFeatures` | — | Toggle optional layout regions and behaviors |
+| Property | Type | Required | Description |
+| ------------ | ------------------------ | -------- | -------------------------------------------- |
+| `variant` | `LayoutVariant` | ✅ | Determines the structural layout mode |
+| `dimensions` | `CoreAppShellDimensions` | — | Override default pixel dimensions |
+| `features` | `CoreAppShellFeatures` | — | Toggle optional layout regions and behaviors |
---
@@ -114,11 +113,11 @@ interface CoreAppShellConfig {
type LayoutVariant = 'header-first' | 'sidebar-first' | 'top-nav';
```
-| 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). |
-| `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. |
+| 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). |
+| `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. |
> [!IMPORTANT]
> When `variant` is set to `top-nav`, the desktop navbar is visually hidden via `collapsed.desktop: true` and width `0`. However, the `` DOM element remains mounted with responsive width props so the mobile drawer continues to function. This is an intentional design choice to avoid conditional DOM removal.
@@ -140,19 +139,18 @@ interface CoreAppShellFeatures {
}
```
-| Property | Type | Default | Description |
-|---|---|---|---|
-| `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. |
-| `withAside` | `boolean` | Auto-detected | Show the right-hand aside panel. Same auto-detection logic as `withUtilityBar`. |
-| `withFooter` | `boolean` | Auto-detected | Show the bottom footer. Same auto-detection logic. |
-| `withDoubleSidebar` | `boolean` | `false` | Enable the **Rail + Panel** double sidebar mode. When `true`, the navbar renders `sidebarRail` and `sidebarPanel` slots instead of the single `sidebar` slot. |
-| `persistState` | `boolean` | `true` (implied) | Persist sidebar variant (`expanded`/`mini`/`hidden`) to `localStorage` via `useLocalStorage`. Set to `false` for demos or ephemeral layouts. |
-| `zIndex` | `number` | `200` | Base z-index passed to Mantine's `AppShell`. |
-| `disabled` | `boolean` | `false` | Disables the AppShell layout entirely (renders children without structural chrome). |
+| Property | Type | Default | Description |
+| ------------------------ | ------------------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `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. |
+| `withAside` | `boolean` | Auto-detected | Show the right-hand aside panel. Same auto-detection logic as `withUtilityBar`. |
+| `withFooter` | `boolean` | Auto-detected | Show the bottom footer. Same auto-detection logic. |
+| `withDoubleSidebar` | `boolean` | `false` | Enable the **Rail + Panel** double sidebar mode. When `true`, the navbar renders `sidebarRail` and `sidebarPanel` slots instead of the single `sidebar` slot. |
+| `persistState` | `boolean` | `true` (implied) | Persist sidebar variant (`expanded`/`mini`/`hidden`) to `localStorage` via `useLocalStorage`. Set to `false` for demos or ephemeral layouts. |
+| `zIndex` | `number` | `200` | Base z-index passed to Mantine's `AppShell`. |
+| `disabled` | `boolean` | `false` | Disables the AppShell layout entirely (renders children without structural chrome). |
-> [!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.
+> [!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.
---
@@ -169,14 +167,14 @@ interface CoreAppShellDimensions {
}
```
-| Property | Type | Default | Description |
-|---|---|---|---|
-| `utilityBarHeight` | `number \| string` | `32` | Height of the utility bar strip above the header |
-| `headerHeight` | `number \| string` | `60` | Height of the main header |
-| `sidebarWidth` | `number \| string` | `260` | Width of the expanded sidebar |
-| `sidebarMiniWidth` | `number \| string` | `80` | Width of the sidebar in `mini` collapse mode |
-| `sidebarRailWidth` | `number \| string` | `54` | Width of the icon rail in double-sidebar mode |
-| `asideWidth` | `number \| string` | `260` | Width of the right-hand aside panel |
+| Property | Type | Default | Description |
+| ------------------ | ------------------ | ------- | ------------------------------------------------ |
+| `utilityBarHeight` | `number \| string` | `32` | Height of the utility bar strip above the header |
+| `headerHeight` | `number \| string` | `60` | Height of the main header |
+| `sidebarWidth` | `number \| string` | `260` | Width of the expanded sidebar |
+| `sidebarMiniWidth` | `number \| string` | `80` | Width of the sidebar in `mini` collapse mode |
+| `sidebarRailWidth` | `number \| string` | `54` | Width of the icon rail in double-sidebar mode |
+| `asideWidth` | `number \| string` | `260` | Width of the right-hand aside panel |
> [!NOTE]
> All dimension values accept both pixel numbers (e.g., `260`) and CSS strings (e.g., `'20rem'`). When both `headerHeight` and `utilityBarHeight` are numbers, they are summed directly. When either is a string, the engine wraps them in a `calc()` expression automatically.
@@ -200,16 +198,16 @@ interface CoreAppShellSlots {
}
```
-| Slot | Location | Notes |
-|---|---|---|
-| `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 `` for mobile toggle (use `useCoreAppShell()` context). |
-| `sidebar` | Desktop navbar body (single-sidebar mode) | Ignored when `withDoubleSidebar` is `true` — use `sidebarRail` + `sidebarPanel` instead. |
-| `sidebarMobile` | Mobile drawer content | Falls back to `sidebar` if not provided. Use this to render a simplified mobile-specific navigation. |
-| `sidebarRail` | Narrow icon rail (double-sidebar mode) | Only rendered when `withDoubleSidebar` is `true`. Separated from `sidebarPanel` by a 1px border. |
-| `sidebarPanel` | Contextual panel beside the rail (double-sidebar mode) | Collapsible via `toggleNavbarPanel()`. Only rendered when `withDoubleSidebar` is `true` and `navbarPanelOpened` is `true`. |
-| `aside` | Right-hand panel | Collapsible via `toggleAside()`. Only rendered when `withAside` is enabled. |
-| `footer` | Bottom application footer | In `header-first` mode, the footer is inset between sidebar and aside. In `sidebar-first` mode, it spans the full width. |
+| Slot | Location | Notes |
+| --------------- | --------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
+| `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 `` for mobile toggle (use `useCoreAppShell()` context). |
+| `sidebar` | Desktop navbar body (single-sidebar mode) | Ignored when `withDoubleSidebar` is `true` — use `sidebarRail` + `sidebarPanel` instead. |
+| `sidebarMobile` | Mobile drawer content | Falls back to `sidebar` if not provided. Use this to render a simplified mobile-specific navigation. |
+| `sidebarRail` | Narrow icon rail (double-sidebar mode) | Only rendered when `withDoubleSidebar` is `true`. Separated from `sidebarPanel` by a 1px border. |
+| `sidebarPanel` | Contextual panel beside the rail (double-sidebar mode) | Collapsible via `toggleNavbarPanel()`. Only rendered when `withDoubleSidebar` is `true` and `navbarPanelOpened` is `true`. |
+| `aside` | Right-hand panel | Collapsible via `toggleAside()`. Only rendered when `withAside` is enabled. |
+| `footer` | Bottom application footer | In `header-first` mode, the footer is inset between sidebar and aside. In `sidebar-first` mode, it spans the full width. |
---
@@ -221,22 +219,21 @@ The `useCoreAppShell()` hook provides access to all layout state and toggle meth
import { useCoreAppShell } from '@repo/ui/components';
```
-| Property / Method | Type | Description |
-|---|---|---|
-| `mobileOpened` | `boolean` | Whether the mobile drawer is currently open |
-| `desktopOpened` | `boolean` | Whether the desktop sidebar is expanded (only applies when `desktopCollapseVariant` is `'hide'`) |
-| `sidebarVariant` | `SidebarVariant` | Current sidebar mode: `'expanded'` \| `'mini'` \| `'hidden'` |
-| `asideOpened` | `boolean` | Whether the aside panel is currently visible |
-| `navbarPanelOpened` | `boolean` | Whether the secondary panel in double-sidebar mode is expanded |
-| `config` | `CoreAppShellConfig` | Read-only access to the current layout configuration |
-| `toggleMobile()` | `() => void` | Toggle the mobile drawer open/closed |
-| `toggleDesktop()` | `() => void` | Toggle the desktop sidebar open/closed |
-| `toggleAside()` | `() => void` | Toggle the aside panel visibility |
-| `toggleNavbarPanel()` | `() => void` | Toggle the double-sidebar panel open/closed |
-| `setSidebarVariant()` | `(variant: SidebarVariant) => void` | Programmatically set the sidebar to `'expanded'`, `'mini'`, or `'hidden'` |
+| Property / Method | Type | Description |
+| --------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------ |
+| `mobileOpened` | `boolean` | Whether the mobile drawer is currently open |
+| `desktopOpened` | `boolean` | Whether the desktop sidebar is expanded (only applies when `desktopCollapseVariant` is `'hide'`) |
+| `sidebarVariant` | `SidebarVariant` | Current sidebar mode: `'expanded'` \| `'mini'` \| `'hidden'` |
+| `asideOpened` | `boolean` | Whether the aside panel is currently visible |
+| `navbarPanelOpened` | `boolean` | Whether the secondary panel in double-sidebar mode is expanded |
+| `config` | `CoreAppShellConfig` | Read-only access to the current layout configuration |
+| `toggleMobile()` | `() => void` | Toggle the mobile drawer open/closed |
+| `toggleDesktop()` | `() => void` | Toggle the desktop sidebar open/closed |
+| `toggleAside()` | `() => void` | Toggle the aside panel visibility |
+| `toggleNavbarPanel()` | `() => void` | Toggle the double-sidebar panel open/closed |
+| `setSidebarVariant()` | `(variant: SidebarVariant) => void` | Programmatically set the sidebar to `'expanded'`, `'mini'`, or `'hidden'` |
-> [!WARNING]
-> `useCoreAppShell()` **must** be called from within a `` 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.
+> [!WARNING] > `useCoreAppShell()` **must** be called from within a `` 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: ,
sidebar: (
-
-
+
+
),
}}
@@ -300,7 +301,9 @@ function AppHeader() {
- Enterprise Dashboard
+
+ Enterprise Dashboard
+
);
@@ -393,7 +396,9 @@ function App() {
),
sidebarPanel: (
- Navigation
+
+ Navigation
+
{/* Contextual links based on active rail icon */}
),
@@ -429,14 +434,17 @@ function ShellDemo() {
const [collapseVariant, setCollapseVariant] = useState('hide');
const [withDoubleSidebar, setWithDoubleSidebar] = useState(false);
- const config: CoreAppShellConfig = useMemo(() => ({
- variant: layoutVariant,
- features: {
- desktopCollapseVariant: collapseVariant,
- withDoubleSidebar,
- persistState: false,
- },
- }), [layoutVariant, collapseVariant, withDoubleSidebar]);
+ const config: CoreAppShellConfig = useMemo(
+ () => ({
+ variant: layoutVariant,
+ features: {
+ desktopCollapseVariant: collapseVariant,
+ withDoubleSidebar,
+ persistState: false,
+ },
+ }),
+ [layoutVariant, collapseVariant, withDoubleSidebar],
+ );
return (
, sidebar: }}>
@@ -466,13 +474,13 @@ interface CorePageContainerProps extends ContainerProps {
}
```
-| Prop | Type | Default | Description |
-|---|---|---|---|
-| `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)`. |
-| `px` | `MantineSpacing` | `'md'` | Horizontal padding for both the header and content areas |
-| `py` | `MantineSpacing` | `'md'` | Vertical padding for both the header and content areas |
-| _...rest_ | `ContainerProps` | — | All other Mantine `Container` props are forwarded to the content region |
+| Prop | Type | Default | Description |
+| -------------- | ---------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
+| `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)`. |
+| `px` | `MantineSpacing` | `'md'` | Horizontal padding for both the header and content areas |
+| `py` | `MantineSpacing` | `'md'` | Vertical padding for both the header and content areas |
+| _...rest_ | `ContainerProps` | — | All other Mantine `Container` props are forwarded to the content region |
### Usage
@@ -482,7 +490,9 @@ interface CorePageContainerProps extends ContainerProps {
stickyHeader
headerSlot={
- Users
+
+ Users
+
}
@@ -513,12 +523,12 @@ In `sidebar-first` mode, the footer spans the full viewport width (`left: 0; rig
### Z-Index Strategy
-| Element | `header-first` | `sidebar-first` |
-|---|---|---|
+| Element | `header-first` | `sidebar-first` |
+| --------------- | --------------- | --------------- |
| AppShell (base) | `200` (default) | `200` (default) |
-| Navbar | `105` | `100` |
-| Aside | `105` | `100` |
-| Footer | `100` | `100` |
+| Navbar | `105` | `100` |
+| Aside | `105` | `100` |
+| Footer | `100` | `100` |
The elevated `105` z-index for navbar/aside in `header-first` mode ensures they render above the footer, which is positioned at `100`.
diff --git a/apps/docs-dev/src/packages/ui/FORM-COMPONENTS.md b/apps/docs-dev/src/packages/ui/FORM-COMPONENTS.md
index 057e182..a9961b8 100644
--- a/apps/docs-dev/src/packages/ui/FORM-COMPONENTS.md
+++ b/apps/docs-dev/src/packages/ui/FORM-COMPONENTS.md
@@ -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.
-> **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`
+> **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`
---
@@ -68,17 +67,17 @@ withRHF(displayName, MantineComponent, options?)
The factory accepts three arguments:
-| Argument | Type | Description |
-|---|---|---|
-| `displayName` | `string` | React DevTools name (e.g., `"FieldTextInput"`) |
-| `MantineComponent` | `ComponentType` | The raw Mantine component |
-| `options` | `WithRHFOptions` | Optional config for special components |
+| Argument | Type | Description |
+| ------------------ | ---------------- | ---------------------------------------------- |
+| `displayName` | `string` | React DevTools name (e.g., `"FieldTextInput"`) |
+| `MantineComponent` | `ComponentType` | The raw Mantine component |
+| `options` | `WithRHFOptions` | Optional config for special components |
#### Options
-| Option | Default | Description |
-|---|---|---|
-| `isCheckType` | `false` | Use `checked` instead of `value` (for Checkbox, Switch) |
+| Option | Default | Description |
+| ----------------- | ------- | ----------------------------------------------------------------------------------------- |
+| `isCheckType` | `false` | Use `checked` instead of `value` (for Checkbox, Switch) |
| `requiresWrapper` | `false` | Wrap in `Input.Wrapper` for error display (for ColorPicker, SegmentedControl, Chip.Group) |
### Naming Conventions
@@ -144,7 +143,6 @@ import { withRHF } from '../withRHF';
export const FieldTextInput = withRHF('FieldTextInput', TextInput);
```
-
---
## Performance & Memoization
@@ -153,10 +151,10 @@ export const FieldTextInput = withRHF('FieldTextInput', TextInpu
In enterprise ERP forms with **1500+ fields**, performance is critical:
-| Technique | What it prevents | Cost |
-|---|---|---|
-| **`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) |
+| Technique | What it prevents | Cost |
+| ------------------- | -------------------------------------------------------------------------- | ----------------------------------------------- |
+| **`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) |
Together, they achieve **O(1) render cost per keystroke** regardless of form size.
@@ -185,10 +183,13 @@ Encode Zod errors as JSON with a translation key:
```tsx
const schema = z.object({
- name: z.string().min(3, JSON.stringify({
- key: 'validation:min_length',
- values: { min: 3 },
- })),
+ name: z.string().min(
+ 3,
+ JSON.stringify({
+ key: 'validation:min_length',
+ values: { min: 3 },
+ }),
+ ),
});
// Error displayed: t('validation:min_length', { min: 3 })
// → "Minimum 3 characters" (from validation namespace)
@@ -311,23 +312,23 @@ function ExampleForm() {
## Validator Bank Reference
-The `registry.validator.ts` provides a set of pre-configured atomic validators returning modified Zod schemas that automatically emit translated JSON payloads.
+The `registry.validator.ts` provides a set of pre-configured atomic validators returning modified Zod schemas that automatically emit translated JSON payloads.
### Available Atomic Validators
-| Category | Validator | Target Type | Description |
-|---|---|---|---|
-| **Numeric** | `minValue(min, field?)` | `ZodNumber` | Minimum numeric value |
-| **Numeric** | `maxValue(max, field?)` | `ZodNumber` | Maximum numeric value |
-| **Numeric** | `rangeValue(min, max, field?)` | `ZodNumber` | Restricts value between `min` and `max` limits |
-| **Numeric** | `positiveNumber(field?)` | `ZodNumber` | Restricts to positive numbers |
-| **String** | `minLength(len, field?)` | `ZodString` | Minimum string character length |
-| **String** | `maxLength(len, field?)` | `ZodString` | Maximum string character length |
-| **String** | `rangeLength(min, max, field?)` | `ZodString` | Restricts string length between `min` and `max` bounds |
-| **Security** | `simplePassword(min)` | `ZodString` | Checks password string length bounds only |
-| **Security** | `complexPassword(min)` | `ZodString` | Enforces length, 1 uppercase, 1 lowercase, 1 number, and 1 special char |
-| **Technical** | `emailValidator()` | `ZodString` | Standard email format |
-| **Technical** | `phoneValidator()` | `ZodString` | Enforces Indonesian (+62) phone number format |
+| Category | Validator | Target Type | Description |
+| ------------- | ------------------------------- | ----------- | ----------------------------------------------------------------------- |
+| **Numeric** | `minValue(min, field?)` | `ZodNumber` | Minimum numeric value |
+| **Numeric** | `maxValue(max, field?)` | `ZodNumber` | Maximum numeric value |
+| **Numeric** | `rangeValue(min, max, field?)` | `ZodNumber` | Restricts value between `min` and `max` limits |
+| **Numeric** | `positiveNumber(field?)` | `ZodNumber` | Restricts to positive numbers |
+| **String** | `minLength(len, field?)` | `ZodString` | Minimum string character length |
+| **String** | `maxLength(len, field?)` | `ZodString` | Maximum string character length |
+| **String** | `rangeLength(min, max, field?)` | `ZodString` | Restricts string length between `min` and `max` bounds |
+| **Security** | `simplePassword(min)` | `ZodString` | Checks password string length bounds only |
+| **Security** | `complexPassword(min)` | `ZodString` | Enforces length, 1 uppercase, 1 lowercase, 1 number, and 1 special char |
+| **Technical** | `emailValidator()` | `ZodString` | Standard email format |
+| **Technical** | `phoneValidator()` | `ZodString` | Enforces Indonesian (+62) phone number format |
> [!WARNING]
> Always distinguish between `rangeValue` (which bounds the actual numeric integer/float) and `rangeLength` (which bounds the amount of characters in a string).
@@ -343,11 +344,7 @@ import { z } from 'zod';
import { compose, required, minLength, complexPassword } from '@repo/ui/validators';
export const userRegistrationSchema = z.object({
- password: compose(
- z.string(),
- required('Password'),
- complexPassword(8)
- )
+ password: compose(z.string(), required('Password'), complexPassword(8)),
});
```
@@ -361,10 +358,10 @@ Tests must explicitly verify the JSON stringified i18n payload:
it('minValue() should enforce min', () => {
const schema = compose(z.number(), minValue(10, 'Age'));
const res = schema.safeParse(5);
-
+
expect(res.success).toBe(false);
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' } }),
);
});
```
@@ -382,10 +379,10 @@ To decouple complex rendering side-effects from your component's root render fun
The hook supports two cleanup strategies defined by the `mode` parameter:
-| 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). |
-| `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). |
+| 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). |
+| `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). |
### Hook Configuration
@@ -395,7 +392,7 @@ import { useConditionalField } from '@repo/ui/hooks';
export function ExampleForm() {
const { control, setValue, unregister, clearErrors } = useForm();
-
+
const userType = useWatch({ control, name: 'userType' });
const newsletter = useWatch({ control, name: 'newsletter' });
@@ -405,7 +402,7 @@ export function ExampleForm() {
name: 'corporateTaxId',
setValue,
unregister,
- mode: 'unregister'
+ mode: 'unregister',
});
// 2. Reset Mode (Visible but Disabled)
@@ -414,7 +411,7 @@ export function ExampleForm() {
name: 'newsletterEmail',
setValue,
clearErrors,
- mode: 'reset'
+ mode: 'reset',
});
return ;
@@ -423,13 +420,12 @@ export function ExampleForm() {
### Cascading Dropdowns & Reactivity
-When dealing with cascading dependencies (e.g., Department -> Role), changing the parent dropdown should invalidate and reset the child dropdown.
+When dealing with cascading dependencies (e.g., Department -> Role), changing the parent dropdown should invalidate and reset the child dropdown.
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]
-> **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.
->
+> [!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.
+>
> 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.
#### Master Example: Department to Role Cascade
@@ -441,17 +437,21 @@ import { FieldSelect } from '@repo/ui/form';
export function DepartmentForm() {
const { control, setValue, clearErrors } = useForm();
-
+
const department = useWatch({ control, name: 'department' });
const role = useWatch({ control, name: 'role' });
// Derive available options based on the parent state
- const currentRoleOptions = department === 'IT'
- ? [{ value: 'FRONTEND', label: 'Frontend' }, { value: 'BACKEND', label: 'Backend' }]
- : [];
+ const currentRoleOptions =
+ department === 'IT'
+ ? [
+ { value: 'FRONTEND', label: 'Frontend' },
+ { value: 'BACKEND', label: 'Backend' },
+ ]
+ : [];
// 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
useConditionalField({
@@ -460,16 +460,16 @@ export function DepartmentForm() {
setValue,
clearErrors,
mode: 'reset',
- defaultValue: ''
+ defaultValue: '',
});
return (
@@ -863,45 +858,43 @@ Use `withRHF` directly to wrap any Mantine component not included in the library
import { DatePickerInput, type DatePickerInputProps } from '@mantine/dates';
import { withRHF } from '@repo/ui/form';
-export const FieldDatePicker = withRHF(
- 'FieldDatePicker',
- DatePickerInput,
-);
+export const FieldDatePicker = withRHF('FieldDatePicker', DatePickerInput);
```
---
## Component Reference
-| Component | Mantine Source | Type | Notes |
-|---|---|---|---|
-| `FieldTextInput` | `TextInput` | Text | Standard text input |
-| `FieldPasswordInput` | `PasswordInput` | Text | Password with visibility toggle |
-| `FieldTextarea` | `Textarea` | Text | Multi-line text |
-| `FieldNumberInput` | `NumberInput` | Text | Numeric with increment/decrement |
-| `FieldJsonInput` | `JsonInput` | Text | JSON-formatted text |
-| `FieldPinInput` | `PinInput` | Text | PIN/OTP code input |
-| `FieldAutocomplete` | `Autocomplete` | Text | Text input with suggestions |
-| `FieldSelect` | `Select` | Selection | Single-value dropdown |
-| `FieldMultiSelect` | `MultiSelect` | Selection | Multi-value dropdown |
-| `FieldNativeSelect` | `NativeSelect` | Selection | Native `