Document: checklists/phase-13-ui-ux-polish.md

# Phase 13: UI/UX Polish & Design System Enhancement

**Objective**: Polish the existing custom design system to create a modern, accessible UI that attracts both users and developers.

**Current State**: Framework M already has a custom CSS-based design system with dark mode, i18n, and Refine.dev integration. This phase focuses on **enhancement, not replacement**.

---

## 1. Audit Current Design System

### 1.1 Review Existing Implementation

- [ ] Audit `frontend/src/App.css` design tokens:
  - [ ] Color palette (currently sky blue `#0ea5e9`)
  - [ ] Typography (Inter font family)
  - [ ] Spacing scale (xs to xl)
  - [ ] Border radius (sm, md, lg)
  - [ ] Shadows
- [ ] Review dark mode implementation in `ThemeContext`
- [ ] Test current mobile responsiveness
- [ ] Run accessibility audit (Lighthouse, axe-core)

### 1.2 Identify Gaps

- [ ] Document missing components (tooltips, dropdowns, etc.)
- [ ] Identify inconsistent spacing/colors
- [ ] List accessibility issues (contrast, keyboard nav, ARIA)
- [ ] Note missing animations/transitions

---

## 2. Refine Color Palette

### 2.1 Update to Deep Blue (Trust Theme)

- [ ] Replace current sky blue (`#0ea5e9`) with deep blue palette:
  ```css
  --color-primary: #2563eb;        /* Deep blue for trust */
  --color-primary-dark: #1d4ed8;   /* Darker variant */
  --color-primary-light: #3b82f6;  /* Lighter variant */
  ```
- [ ] Update semantic colors:
  - Success: `#059669` (emerald-600)
  - Warning: `#d97706` (amber-600)
  - Error: `#dc2626` (red-600)
  - Info: `#0284c7` (sky-600)

### 2.2 Verify WCAG AA Contrast

- [ ] Test all text/background combinations
- [ ] Ensure minimum 4.5:1 contrast for normal text
- [ ] Ensure minimum 3:1 contrast for UI components
- [ ] Test in both light and dark modes
- [ ] Use tools: WebAIM Contrast Checker, Lighthouse

### 2.3 Update Dark Mode Colors

- [ ] Refine dark mode palette for better contrast
- [ ] Test readability in low-light environments
- [ ] Ensure primary blue works in dark mode (`#38bdf8`)

---

## 3. Typography & Spacing Refinement

### 3.1 Typography Scale

- [ ] Review current font sizes (14px base)
- [ ] Add missing heading sizes if needed
- [ ] Ensure consistent line-heights (1.3 for headings, 1.5 for body)
- [ ] Test readability at different viewport sizes

### 3.2 Spacing Consistency

- [ ] Audit all components for consistent spacing
- [ ] Use spacing scale consistently (xs, sm, md, lg, xl)
- [ ] Fix any hard-coded pixel values
- [ ] Document spacing patterns (e.g., card padding, button spacing)

---

## 4. Component Polish

### 4.1 Buttons

- [ ] Add hover/active/focus states
- [ ] Add loading state (spinner)
- [ ] Add disabled state styling
- [ ] Ensure consistent sizing (height, padding)
- [ ] Add keyboard focus indicators (visible outline)

### 4.2 Form Inputs

- [ ] Polish input focus states (already has blue ring)
- [ ] Add error state styling (red border + message)
- [ ] Add success state styling (green border)
- [ ] Improve placeholder text contrast
- [ ] Add floating labels (optional enhancement)

### 4.3 Tables

- [ ] Add row hover effects (already exists)
- [ ] Add sortable column indicators
- [ ] Add loading skeleton for data tables
- [ ] Improve mobile table responsiveness (card view)
- [ ] Add empty state illustration

### 4.4 Cards

- [ ] Add subtle shadow on hover
- [ ] Ensure consistent border radius
- [ ] Add loading skeleton variant
- [ ] Test card layouts on mobile

### 4.5 Sidebar

- [ ] Polish active state (already uses primary color)
- [ ] Add smooth collapse animation
- [ ] Improve search input styling
- [ ] Add keyboard navigation support
- [ ] Test mobile drawer behavior

### 4.6 Navbar

- [ ] Polish search bar styling
- [ ] Add user menu dropdown
- [ ] Add notification badge
- [ ] Ensure consistent height (56px)

---

## 5. Animations & Micro-interactions

### 5.1 Add CSS Transitions

- [ ] Button hover effects (0.15s ease)
- [ ] Input focus transitions (0.15s ease)
- [ ] Sidebar item hover (0.15s ease)
- [ ] Card hover effects (subtle lift)
- [ ] Modal/drawer slide-in animations

### 5.2 Loading States

- [ ] Add skeleton loaders for:
  - [ ] List view (table rows)
  - [ ] Form view (form fields)
  - [ ] Dashboard (cards)
- [ ] Add spinner component for buttons
- [ ] Add page transition fade

### 5.3 Optimistic UI Updates

- [ ] Instant feedback on create (fade in new row)
- [ ] Instant feedback on update (highlight changed row)
- [ ] Instant feedback on delete (fade out row)
- [ ] Rollback on error with toast notification

---

## 6. Mobile Optimization

### 6.1 Responsive Layouts

- [ ] Test all screens on mobile viewports:
  - [ ] 320px (iPhone SE)
  - [ ] 375px (iPhone 12/13)
  - [ ] 414px (iPhone 12 Pro Max)
  - [ ] 768px (iPad)
- [ ] Ensure sidebar collapses to drawer on mobile
- [ ] Test table responsiveness (switch to card view)
- [ ] Ensure touch targets are min 44px

### 6.2 Mobile-Specific Enhancements

- [ ] Add swipe gestures for sidebar drawer
- [ ] Optimize form layouts for mobile (single column)
- [ ] Add bottom navigation (optional)
- [ ] Test keyboard behavior on mobile browsers

---

## 7. Accessibility (WCAG 2.1 AA)

### 7.1 Color Contrast

- [ ] Run automated contrast checks (Lighthouse)
- [ ] Fix any failing combinations
- [ ] Ensure all interactive elements meet 3:1 minimum
- [ ] Test with color blindness simulators

### 7.2 Keyboard Navigation

- [ ] Test tab order for all pages
- [ ] Add visible focus indicators (already has some)
- [ ] Implement keyboard shortcuts:
  - [ ] `Ctrl+K` for command palette (already exists)
  - [ ] `Esc` to close modals/drawers
  - [ ] Arrow keys for table navigation
  - [ ] `Enter` to activate buttons
- [ ] Add skip navigation links

### 7.3 Screen Reader Support

- [ ] Add ARIA labels to all interactive elements
- [ ] Add ARIA live regions for dynamic content (toasts, notifications)
- [ ] Test with screen readers:
  - [ ] NVDA (Windows)
  - [ ] JAWS (Windows)
  - [ ] VoiceOver (macOS/iOS)
- [ ] Add meaningful alt text for images/icons

### 7.4 Form Accessibility

- [ ] Ensure all inputs have associated labels
- [ ] Add ARIA descriptions for error messages
- [ ] Add required field indicators (`*` or "required" text)
- [ ] Add field help text with `aria-describedby`

---

## 8. Missing Components

### 8.1 Add Essential Components

- [ ] **Tooltip** - For icon buttons and help text
- [ ] **Dropdown Menu** - For user menu, actions
- [ ] **Toast Notifications** - For success/error messages
- [ ] **Modal Dialog** - For confirmations, forms
- [ ] **Badge** - For counts, status indicators
- [ ] **Alert** - For warnings, errors, info messages
- [ ] **Tabs** - For multi-section forms/views
- [ ] **Accordion** - For collapsible sections

### 8.2 Implementation Approach

- [ ] Use vanilla CSS + React state (no library)
- [ ] Follow existing design token patterns
- [ ] Ensure dark mode support
- [ ] Add to component library documentation

---

## 9. Documentation

### 9.1 Design System Documentation

- [ ] Create `docs/design-system.md`:
  - [ ] Color palette with hex codes
  - [ ] Typography scale
  - [ ] Spacing system
  - [ ] Component patterns
  - [ ] Animation principles
  - [ ] Accessibility guidelines

### 9.2 Component Documentation

- [ ] Document each component with:
  - [ ] Usage examples
  - [ ] Props/variants
  - [ ] Accessibility notes
  - [ ] Code snippets

### 9.3 Storybook (Optional)

- [ ] Set up Storybook for component showcase
- [ ] Create stories for all components
- [ ] Add dark mode toggle in Storybook
- [ ] Deploy Storybook for team reference

---

## 10. Performance Optimization

### 10.1 CSS Optimization

- [ ] Remove unused CSS rules
- [ ] Minimize CSS file size
- [ ] Use CSS containment where applicable
- [ ] Optimize font loading (already using Google Fonts)

### 10.2 Bundle Size

- [ ] Analyze bundle size with Vite build
- [ ] Code-split large components
- [ ] Lazy load routes
- [ ] Optimize images (use WebP, lazy loading)

### 10.3 Runtime Performance

- [ ] Test First Contentful Paint (FCP) - target `< 1.5s`
- [ ] Test Time to Interactive (TTI) - target `< 3s`
- [ ] Test Largest Contentful Paint (LCP) - target `< 2.5s`
- [ ] Optimize re-renders (React.memo, useMemo)

---

## 11. Testing

### 11.1 Visual Regression Testing

- [ ] Set up Chromatic or Percy (optional)
- [ ] Create visual snapshots for key pages
- [ ] Test across browsers:
  - [ ] Chrome
  - [ ] Firefox
  - [ ] Safari
  - [ ] Edge

### 11.2 Accessibility Testing

- [ ] Run Lighthouse accessibility audit (target 100)
- [ ] Run axe-core automated tests
- [ ] Manual keyboard-only testing
- [ ] Manual screen reader testing

### 11.3 Cross-Browser Testing

- [ ] Test on Windows (Chrome, Firefox, Edge)
- [ ] Test on macOS (Chrome, Firefox, Safari)
- [ ] Test on iOS (Safari, Chrome)
- [ ] Test on Android (Chrome)

---

## 12. Example Screens Enhancement

### 12.1 Polish Reference Screens

- [ ] **Dashboard** - Add chart widgets, metric cards
- [ ] **List View** - Polish filters, search, pagination
- [ ] **Form View** - Polish validation, error states
- [ ] **Detail View** - Add tabs, related lists
- [ ] **Settings Page** - Add sections, save states

### 12.2 Create Demo Content

- [ ] Add sample data for screenshots
- [ ] Create video walkthrough showing:
  - [ ] Dark mode toggle
  - [ ] Mobile responsiveness
  - [ ] Smooth animations
  - [ ] Accessibility features

---

## Validation Checklist

Before considering Phase 13 complete, verify:

- [ ] Deep blue color palette is consistently applied
- [ ] Dark mode works perfectly across all screens
- [ ] Mobile responsiveness is 100% (tested on real devices)
- [ ] WCAG 2.1 AA compliance verified (Lighthouse score 100)
- [ ] All animations are smooth (60fps)
- [ ] No UI library dependencies (pure CSS + React)
- [ ] Design system is documented
- [ ] Developers say: "Wow, this looks professional!"

---

## Success Metrics

### Before (Current State)
- Custom CSS design system (good foundation)
- Sky blue color palette
- Basic dark mode
- Some mobile responsiveness
- Minimal animations

### After (Target State)
- **Polished design system** with deep blue palette
- **Smooth transitions** and micro-interactions
- **100% mobile responsive** with touch-friendly UI
- **WCAG 2.1 AA compliant** (verified)
- **Professional, modern aesthetic** that attracts users and developers
- **Zero UI library dependencies** - pure custom CSS

---

## Notes

- **No shadcn/ui or Ant Design** - We already have a custom system
- **No Tailwind in Desk UI** - Keep vanilla CSS approach (Studio uses Tailwind)
- **Enhance, don't replace** - Build on existing CSS variables
- **Focus on polish** - Animations, spacing, colors, accessibility
- **Document everything** - Make the design system reusable


Document: checklists/phase-10-production.md

# Phase 10: Production Readiness & Deployment

**Objective**: Optimize for production, add monitoring, implement security best practices, and create deployment configurations.

---

## 1. Performance Optimization

### 1.1 Database Optimization

- [ ] Add database indexes:
  - [ ] Index on `id` (primary key, UUID)
  - [ ] Index on `name` (unique, human-readable identifier)
  - [ ] Index on `owner` (for RLS)
  - [ ] Index on `modified` (for sorting)
  - [ ] Index on foreign keys
  - [ ] Composite indexes for common queries

- [ ] Implement query optimization:
  - [ ] Use `select_in_loading` for relationships
  - [ ] Avoid N+1 queries
  - [ ] Add query logging in dev mode
  - [ ] Analyze slow queries

### 1.2 Caching Strategy

- [ ] Implement multi-level caching:
  - [ ] L1: In-memory cache (per-request)
  - [ ] L2: Redis cache (shared)
  - [ ] Cache metadata (DocType schemas)
  - [ ] Cache permission checks
  - [ ] Cache frequently accessed documents

- [ ] Add cache invalidation:
  - [ ] Invalidate on document update
  - [ ] Invalidate on permission change
  - [ ] Use cache tags for bulk invalidation

### 1.3 Connection Pooling

- [ ] Configure SQLAlchemy pool:
  - [ ] Pool size: 20
  - [ ] Max overflow: 10
  - [ ] Pool timeout: 30s
  - [ ] Pool recycle: 3600s

- [ ] Configure Redis pool:
  - [ ] Max connections: 50

### 1.4 Async Optimization

- [ ] Use `asyncio.gather()` for parallel operations
- [ ] Avoid blocking calls in async functions
- [ ] Use connection pools efficiently

---

## 2. Security Hardening

### 2.1 Input Validation

- [ ] Validate all inputs with Pydantic
- [ ] Sanitize user-provided data
- [ ] Prevent SQL injection (use parameterized queries)
- [ ] Prevent XSS (API-only, no HTML rendering)

### 2.2 Authentication Security

- [ ] Support JWT token validation:
  - [ ] Verify signature
  - [ ] Check expiration
  - [ ] Validate issuer

- [ ] Add rate limiting:
  - [ ] Limit login attempts
  - [ ] Limit API requests per user
  - [ ] Use Redis for rate limit storage

### 2.3 Authorization Security

- [ ] Enforce permissions on all endpoints
- [ ] Apply RLS to all queries
- [ ] Validate object-level permissions
- [ ] Log permission denials

### 2.4 Secrets Management

- [ ] Never commit secrets to Git
- [ ] Use environment variables
- [ ] Support secret managers:
  - [ ] AWS Secrets Manager
  - [ ] Google Secret Manager
  - [ ] HashiCorp Vault

### 2.5 HTTPS & CORS

- [ ] Enforce HTTPS in production
- [ ] Configure CORS properly:
  - [ ] Whitelist allowed origins
  - [ ] Restrict methods and headers

---

## 3. Monitoring & Observability

### 3.1 Logging

- [ ] Implement structured logging:
  - [ ] Use JSON format
  - [ ] Include request ID
  - [ ] Include user context
  - [ ] Log levels: DEBUG, INFO, WARNING, ERROR

- [ ] Log important events:
  - [ ] API requests (with timing)
  - [ ] Database queries (in dev)
  - [ ] Permission checks
  - [ ] Errors and exceptions

### 3.2 Metrics

- [ ] Add Prometheus metrics:
  - [ ] Request count by endpoint
  - [ ] Request duration histogram
  - [ ] Database query duration
  - [ ] Cache hit/miss rate
  - [ ] Job queue length

- [ ] Create `/metrics` endpoint

### 3.3 Tracing

- [ ] Implement OpenTelemetry:
  - [ ] Trace HTTP requests
  - [ ] Trace database queries
  - [ ] Trace background jobs
  - [ ] Export to Jaeger or Zipkin

### 3.4 Health Checks

- [ ] Create `/health` endpoint:
  - [ ] Check database connection
  - [ ] Check Redis connection
  - [ ] Return 200 if healthy, 503 if not

- [ ] Create `/ready` endpoint:
  - [ ] Check if app is ready to serve traffic
  - [ ] Check migrations are up to date

---

## 4. Error Handling & Resilience

### 4.1 Graceful Degradation

- [ ] Handle database failures gracefully
- [ ] Handle Redis failures (fallback to no cache)
- [ ] Retry transient errors

### 4.2 Circuit Breaker

- [ ] Implement circuit breaker for external services:
  - [ ] Webhook calls
  - [ ] Email sending
  - [ ] Print service (Gotenberg)

### 4.3 Timeouts

- [ ] Set timeouts for all operations:
  - [ ] HTTP requests: 30s
  - [ ] Database queries: 10s
  - [ ] Background jobs: 300s

---

## 5. Deployment Configurations

### 5.1 Docker

- [ ] Create production `Dockerfile`:

  ```dockerfile
  FROM python:3.12-slim
  WORKDIR /app
  COPY . .
  RUN uv pip install --no-cache-dir .
  CMD ["m", "start", "--host", "0.0.0.0"]
  ```

- [ ] Multi-stage build for smaller images
- [ ] Use non-root user
- [ ] Add health check

### 5.2 Docker Compose

- [ ] Create `docker-compose.yml`:
  - [ ] API service
  - [ ] Worker service
  - [ ] PostgreSQL
  - [ ] Redis
  - [ ] Gotenberg (for PDFs)

- [ ] Add volumes for persistence
- [ ] Add networks for isolation

### 5.3 Kubernetes

- [ ] Create Kubernetes manifests:
  - [ ] Deployment for API
  - [ ] Deployment for Worker
  - [ ] StatefulSet for PostgreSQL
  - [ ] StatefulSet for Redis
  - [ ] Service for API
  - [ ] Ingress for routing

- [ ] Add resource limits:
  - [ ] CPU: 500m - 2000m
  - [ ] Memory: 512Mi - 2Gi

- [ ] Add liveness and readiness probes

### 5.4 Cloud Platforms

- [ ] Create deployment guides for:
  - [ ] Google Cloud Run
  - [ ] AWS ECS/Fargate
  - [ ] Azure Container Apps
  - [ ] Heroku
  - [ ] Railway
  - [ ] Render

---

## 6. CI/CD Pipeline

### 6.1 GitLab CI

- [ ] Configure `.gitlab-ci.yml` (already exists in repo):

  ```yaml
  stages:
    - test
    - build
    - deploy

  test:
    stage: test
    script:
      - uv sync
      - uv run pytest
      - uv run mypy --strict .
      - uv run ruff check .
    rules:
      - if: $CI_PIPELINE_SOURCE == "merge_request_event"

  build:
    stage: build
    script:
      - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
      - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    rules:
      - if: $CI_COMMIT_BRANCH == "main"

  deploy-staging:
    stage: deploy
    script:
      - kubectl set image deployment/app app=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    environment: staging
    rules:
      - if: $CI_COMMIT_BRANCH == "main"

  deploy-production:
    stage: deploy
    script:
      - kubectl set image deployment/app app=$CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
    environment: production
    rules:
      - if: $CI_COMMIT_TAG =~ /^v.*/
  ```

### 6.2 Pre-commit Hooks

- [ ] Setup pre-commit:
  - [ ] Run ruff formatter
  - [ ] Run mypy
  - [ ] Run tests

### 6.3 Release Versioning & Publishing

- [ ] Adopt Semantic Versioning (SemVer):
  - [ ] `MAJOR.MINOR.PATCH` format
  - [ ] Document breaking change policy
  - [ ] Use `0.x.y` for pre-1.0 releases

- [ ] CHANGELOG management:
  - [ ] Create `CHANGELOG.md` following [Keep a Changelog](https://keepachangelog.com) format
  - [ ] Sections: Added, Changed, Deprecated, Removed, Fixed, Security
  - [ ] Option: Use [git-cliff](https://git-cliff.org/) for automation
  - [ ] Link CHANGELOG to documentation site

- [ ] Conventional Commits (optional but recommended):
  - [ ] `feat:` → MINOR bump
  - [ ] `fix:` → PATCH bump
  - [ ] `feat!:` or `BREAKING CHANGE:` → MAJOR bump
  - [ ] Setup commitlint for enforcement

- [ ] Release workflow (add to `.gitlab-ci.yml`):

  ```yaml
  release:
    stage: deploy
    script:
      - uv build
      - uv publish
    variables:
      UV_PUBLISH_TOKEN: $PYPI_TOKEN
    rules:
      - if: $CI_COMMIT_TAG =~ /^v.*/

  release-notes:
    stage: deploy
    image: registry.gitlab.com/gitlab-org/release-cli:latest
    script:
      - echo "Creating release for $CI_COMMIT_TAG"
    release:
      tag_name: $CI_COMMIT_TAG
      description: ./CHANGELOG.md
    rules:
      - if: $CI_COMMIT_TAG =~ /^v.*/
  ```

- [ ] Version sources:
  - [ ] Single source of truth in `pyproject.toml`
  - [ ] Use dynamic versioning from git tags OR `__version__` in package
  - [ ] Expose version via `m --version` CLI

- [ ] Pre-release versions:
  - [ ] Support alpha/beta/rc: `1.0.0-alpha.1`, `1.0.0-beta.2`, `1.0.0-rc.1`
  - [ ] Publish pre-releases to TestPyPI first

### 6.4 Git Branching Strategy

> [!NOTE]
> Framework M's hexagonal architecture (adapters are swappable, not forked) enables
> a **simpler branching model** than Frappe's `version-XX` branches. Breaking changes
> are isolated to Protocol interfaces, not scattered across the codebase.

- [ ] **Trunk-Based Development** (recommended for M):

  ```
  main (default)
    │
    ├── feature/xyz        # Short-lived feature branches
    ├── fix/issue-123      # Bug fix branches
    │
    └── Tags: v0.1.0, v0.2.0, v1.0.0, v1.0.1, v1.1.0 ...
  ```

- [ ] Branch naming conventions:
  - [ ] `main` — Always deployable, latest development
  - [ ] `feature/<name>` — New features (merge to main via PR)
  - [ ] `fix/<issue-id>` — Bug fixes (merge to main via PR)
  - [ ] `release/v<major>.<minor>` — Created only when LTS support needed

- [ ] **Release branches** (only for LTS):

  ```
  main
    │
    ├─── v1.0.0 (tag)
    │
    └─── release/v1  ← Created when v2.0.0 is released
           │
           ├─── v1.0.1 (backport tag)
           └─── v1.0.2 (backport tag)
  ```

- [ ] **LTS (Long-Term Support) Policy**:
  - [ ] Support **last 2 major versions** (not 3 like Frappe — M is simpler)
  - [ ] Security fixes: Backport to all supported versions
  - [ ] Bug fixes: Backport to latest minor of each supported major
  - [ ] Features: Only on `main`, no backports

- [ ] Backporting workflow:
  - [ ] Fix on `main` first (always forward-fix)
  - [ ] Cherry-pick to `release/vX` branch
  - [ ] Tag patch release: `vX.Y.Z`
  - [ ] Tool: Use `git cherry-pick -x` to track source commit

- [ ] **Why simpler than Frappe?**
  - [ ] Hexagonal architecture = Adapters can be versioned independently
  - [ ] No DB schema in core = Migrations are app-specific, not framework-specific
  - [ ] Protocol stability = Breaking changes are explicit, not hidden
  - [ ] Apps can pin framework version = No forced upgrade cascades

---

## 7. Database Migration Strategy

> [!IMPORTANT]
> Migrations are generated by Alembic (Phase 02). This section defines **when** and **how** to apply them safely.

### 7.1 Change Classification

| Category      | Examples                                               | Auto-Apply (Dev)? | Production Strategy                 |
| ------------- | ------------------------------------------------------ | ----------------- | ----------------------------------- |
| **Safe**      | Add nullable column, add table, add index              | ✅ Yes            | Apply directly                      |
| **Review**    | Add non-nullable column, change type (compatible)      | ⚠ Generate only  | Review → Staging → Prod             |
| **Dangerous** | Drop column, rename column, change type (incompatible) | ❌ No             | Manual migration with data handling |

### 7.2 Safe Changes (Auto-Migration in Dev Mode)

These changes are automatically applied in development (`m dev`):

- [ ] **Add table**: New DocType → new table
- [ ] **Add nullable column**: New optional field → `ALTER TABLE ADD COLUMN ... NULL`
- [ ] **Add index**: `Meta.indexes` → `CREATE INDEX`
- [ ] **Add foreign key**: Link field → `ALTER TABLE ADD CONSTRAINT`

```python
# Dev mode: auto-applies safe migrations
m dev          # Detects changes, generates & applies migration

# Production: never auto-apply
m prod         # Requires explicit `m migrate` first
```

### 7.3 Review-Required Changes

These changes generate migrations but require human review:

- [ ] **Add non-nullable column without default**:
  - [ ] Migration will fail if table has existing rows
  - [ ] Solution: Add with default → backfill → remove default

- [ ] **Change column type (compatible)**:
  - [ ] `str` → `Text` (widening): Usually safe
  - [ ] `int` → `bigint` (widening): Usually safe
  - [ ] Review for edge cases

```python
# CLI shows warning for review-required changes
$ m migrate:create add_phone_field

⚠  Review required: 'phone' is non-nullable without default
    Existing rows will cause migration to fail.

    Recommendation:
    1. Add field with default: phone: str = ""
    2. Run migration
    3. Backfill data
    4. Remove default if desired
```

### 7.4 Dangerous Changes (Manual Handling Required)

These changes require explicit data migration code:

#### Drop Column

```python
# Migration: drop_old_field.py
def upgrade():
    # Step 1: Ensure no code references the column
    # Step 2: Drop the column
    op.drop_column('invoice', 'old_field')

def downgrade():
    # Re-add column (data is lost!)
    op.add_column('invoice', sa.Column('old_field', sa.String(255)))
```

#### Rename Column

```python
# Migration: rename_customer_to_party.py
def upgrade():
    # Rename preserves data
    op.alter_column('invoice', 'customer', new_column_name='party')

def downgrade():
    op.alter_column('invoice', 'party', new_column_name='customer')
```

#### Type Change (Incompatible)

```python
# Migration: age_str_to_int.py
def upgrade():
    # Step 1: Add new column
    op.add_column('person', sa.Column('age_int', sa.Integer(), nullable=True))

    # Step 2: Migrate data with transformation
    connection = op.get_bind()
    connection.execute(text("""
        UPDATE person
        SET age_int = CASE
            WHEN age ~ '^[0-9]+$' THEN age::integer
            ELSE NULL
        END
    """))

    # Step 3: Drop old column
    op.drop_column('person', 'age')

    # Step 4: Rename new column
    op.alter_column('person', 'age_int', new_column_name='age')

def downgrade():
    # Reverse transformation
    op.alter_column('person', 'age', type_=sa.String(10),
                    postgresql_using='age::varchar')
```

### 7.5 Data Migration Patterns

#### Pattern A: Backfill in Migration (Small Tables)

```python
def upgrade():
    # For tables `< 100k` rows: inline backfill
    op.add_column('user', sa.Column('full_name', sa.String(255)))
    connection = op.get_bind()
    connection.execute(text("""
        UPDATE user SET full_name = first_name || ' ' || last_name
    """))
```

#### Pattern B: Background Job (Large Tables)

```python
# Migration: add_computed_field.py
def upgrade():
    # Step 1: Add nullable column
    op.add_column('invoice', sa.Column('tax_amount', sa.Numeric(), nullable=True))
    # Step 2: Document: run `m job:run backfill_tax_amounts` after migration

# Job: jobs/backfill_tax_amounts.py
@job
async def backfill_tax_amounts(batch_size: int = 1000):
    """Backfill tax_amount for existing invoices."""
    async with uow_factory() as uow:
        invoices = await repo.list(
            filters=[Filter("tax_amount", "is", None)],
            limit=batch_size
        )
        for inv in invoices.items:
            inv.tax_amount = inv.total * inv.tax_rate
            await repo.save(uow.session, inv)
        await uow.commit()
```

#### Pattern C: Two-Phase Migration (Zero Downtime)

```
Phase 1 (Deploy v1.1):
  - Add new column (nullable)
  - Code writes to BOTH old and new columns
  - Background job backfills new column

Phase 2 (Deploy v1.2):
  - Code reads from new column only
  - Drop old column in migration
```

### 7.6 Zero-Downtime Migrations

- [ ] **Never** drop columns in the same deploy as code changes
- [ ] **Always** add columns as nullable first
- [ ] **Always** backfill data before making non-nullable
- [ ] **Always** deploy code changes before dropping columns

```
Deploy Timeline:

  v1.0: old_field exists, code uses old_field
         │
  v1.1: new_field added (nullable), code writes to BOTH
         │ ← Run backfill job
  v1.2: code reads from new_field only
         │
  v1.3: drop old_field migration
```

### 7.7 Migration Workflow (Production)

```bash
# 1. Generate migration (dev environment)
m migrate:create add_user_phone_field

# 2. Review generated migration
cat alembic/versions/xxxx_add_user_phone_field.py

# 3. Test on local/staging
m migrate
m migrate:rollback  # Verify rollback works

# 4. Commit migration file to Git
git add alembic/versions/xxxx_add_user_phone_field.py
git commit -m "feat: add phone field to user"

# 5. Deploy to staging
# CI/CD runs: m migrate

# 6. Verify staging works

# 7. Deploy to production
# CI/CD runs: m migrate
```

### 7.8 CLI Commands Reference

| Command                     | Description                  |
| --------------------------- | ---------------------------- |
| `m migrate`                 | Apply all pending migrations |
| `m migrate:status`          | Show current migration state |
| `m migrate:create \<name\>` | Generate new migration       |
| `m migrate:rollback`        | Rollback last migration      |
| `m migrate:history`         | Show migration history       |
| `m migrate:heads`           | Show latest migration(s)     |

---

## 8. Backup & Recovery

### 8.1 Database Backups

- [ ] Automated daily backups
- [ ] Store backups in S3 or equivalent
- [ ] Retention policy (30 days)
- [ ] Test restore process

### 8.2 Disaster Recovery

- [ ] Document recovery procedures
- [ ] Test recovery regularly
- [ ] RTO (Recovery Time Objective): 4 hours
- [ ] RPO (Recovery Point Objective): 24 hours

---

## 9. Scaling Strategy

### 9.1 Horizontal Scaling

- [ ] API servers: Stateless, scale horizontally
- [ ] Workers: Scale based on queue length
- [ ] Database: Use read replicas for read-heavy workloads

### 9.2 Load Balancing

- [ ] Use load balancer (Nginx, ALB)
- [ ] Health check endpoints
- [ ] Session affinity not required (stateless)

---

## 10. Documentation

### 10.1 Deployment Guide

- [ ] Write step-by-step deployment guide
- [ ] Include environment variables reference
- [ ] Include troubleshooting section

### 10.2 Operations Runbook

- [ ] Document common operations:
  - [ ] Scaling up/down
  - [ ] Running migrations
  - [ ] Backup and restore
  - [ ] Monitoring and alerts

---

## 11. Testing

### 11.1 Load Testing

- [ ] Use Locust or k6 for load testing
- [ ] Test scenarios:
  - [ ] 1000 concurrent users
  - [ ] 10,000 requests/minute
  - [ ] Sustained load for 1 hour

- [ ] Measure:
  - [ ] Response times (p50, p95, p99)
  - [ ] Error rate
  - [ ] Resource usage

### 11.2 Security Testing

- [ ] Run security scans:
  - [ ] OWASP ZAP
  - [ ] Dependency vulnerability scan
  - [ ] Container image scan

---

## Validation Checklist

Final checks before production:

- [ ] All tests pass (unit, integration, E2E)
- [ ] Load testing shows acceptable performance
- [ ] Security scans show no critical issues
- [ ] Monitoring and alerts are configured
- [ ] Backups are automated and tested
- [ ] Documentation is complete
- [ ] Deployment pipeline works
- [ ] Health checks return 200
- [ ] Logs are structured and useful
- [ ] Secrets are not in code

---

## Anti-Patterns to Avoid

❌ **Don't**: Deploy without testing
✅ **Do**: Test thoroughly in staging

❌ **Don't**: Ignore monitoring
✅ **Do**: Set up alerts for critical metrics

❌ **Don't**: Hardcode configuration
✅ **Do**: Use environment variables

❌ **Don't**: Run as root in containers
✅ **Do**: Use non-root user

❌ **Don't**: Skip backups
✅ **Do**: Automate and test backups regularly


Document: checklists/phase-10-erp-features.md

# Phase 10: ERP Enterprise Features

**Objective**: Implement scalable ERP features using CQRS for Reporting, Analytics, and Integrations. Ensure "Zero Cliff" scalability from startup to enterprise.

---

## 1. Analytics & Read Models (CQRS)

**Concept**: Separate the "Write Model" (Transactional DB) from the "Read Model" (Reporting DB / Analytics Engine).

### 1.1 Read Model Protocol

- [x] Create `src/framework_m/core/interfaces/read_model.py`:
- [x] Define `ReadModelProtocol`:
  - [x] `async def project(event: Event) -> None`
  - [x] `async def query(filters: dict) -> list[dict]`
  - [x] `async def rebuild() -> None`

### 1.2 Enterprise Projectors

- [x] Implement `Projector` (Event Handler):
  - [x] Subscribe to `doc.created`, `doc.updated`
  - [x] Transform transactional data into "Read Optimized" format
  - [x] Upsert into Read Store (Postgres Read Replica, ClickHouse, Elastic)

### 1.3 Report Engine (Scalable)

- [x] Create `Report` DocType:
  - [x] `report_type`:
    - [x] `Code Report` (SQL file in repo or Python Class - **Indie Mode**)
    - [x] `Analytics Report` (Queries Read Model / OLAP - **Enterprise Mode**)
  - [x] `data_source`: SQL / Elastic / ClickHouse adapter

---

## 2. Webhook System (Integration)

**Objective**: Zero-code integration with 3rd party tools (Slack, Zapier).

### 2.1 Webhook DocType

- [x] Create `src/framework_m/public/webhook.py`
- [x] Define `Webhook` DocType:
  - [x] `doctype: str` - Trigger DocType
  - [x] `event: str` - Event name (after_save, on_submit)
  - [x] `url: str` - Target Endpoint
  - [x] `headers: dict` - Auth/Custom Headers
  - [x] `condition: str | None` - JMESPath or SimpleEval expression (No `eval()`)
  - [x] `payload_transform: str | None` - Jinja2 template for JSON payload

### 2.2 Webhook Dispatcher

- [x] Implement `WebhookListener`:
  - [x] Subscribe to `doc.*` events
  - [x] Queue background job `fire_webhook`
  - [x] **Resilience**: Exponential Backoff (handled by Job Queue)

### 2.3 Webhook Visualization (Native UI)

- [x] **Webhook Log DocType** (defined in Phase 04):
  - [x] Stores Request/Response/Error.
  - [x] Status: `Success` (Green), `Failed` (Red).
- [x] **Desk Integration**:
  - [x] Add "Recent Logs" table to `Webhook` Form View.
  - [x] Action: "Retry Now" button (re-queues the job).

---

## 3. Data Import / Export (Bulk)

- [x] Defined in Phase 05 CLI (See `m import --bypass-controllers`)


Document: checklists/phase-06-builtin-doctypes.md

# Phase 06: Built-in DocTypes & Core Features

**Objective**: Implement essential built-in DocTypes (User, Role, Permission) and core features like file attachments, audit logging, and printing.

---

## 1. Identity & Access Management (PIM)

### 1.1 Identity Provider Protocol
- [x] Create `IdentityProtocol` (Interface):
  - [x] `get_user(id: str) -> User`
  - [x] `authenticate(credentials) -> Token`
  - [x] `get_attributes(user) -> dict[str, Any]` (ABAC replacement for get_roles)

### 1.2 User DocTypes (Pluggable)
- [x] **Mode A: Local (Indie)**:
  - [x] `LocalUser` (SQL Table): Stores email, password_hash, full_name.
  - [x] `LocalIdentityAdapter`: argon2 password hashing, JWT generation.

- [x] **Mode B: Federated (Enterprise)**:
  - [x] `FederatedIdentityAdapter` (Proxy):
    - [x] No SQL Table required.
    - [x] Hydrates from Auth Header (`X-User-ID`, `X-Email`) via `hydrate_from_headers()`.
    - [x] `UserPreferences` DocType stores settings locally.

### 1.3 User Controller (Adapter)
- [x] Implement `UserManager`:
  - [x] Delegates to configured `IdentityProvider` via `IdentityProtocol`.
  - [x] Abstraction layer: `user = await user_manager.get(id)`.
  - [x] Additional `create()` method for Indie mode with password hashing.

### 1.4 Auth API (Indie Mode)
- [x] Create `GET /api/v1/auth/me`:
  - [x] Returns current user context (or 401).
- [x] Create `POST /api/v1/auth/login` (Indie Only):
  - [x] Accepts `username`, `password`.
  - [x] Validates against `LocalUser` via `UserManager.authenticate()`.
  - [x] Returns JWT token.
- [x] Create `POST /api/v1/auth/logout`:
  - [x] Returns success message (JWT auth is client-side logout).

### 1.5 Authentication Strategies (Multi-Method)

> [!NOTE]
> Same user can authenticate via multiple methods (browser, API, CLI).
> All strategies resolve to the same `UserContext`.

- [x] Create `AuthenticationProtocol` (Interface):
  - [x] `supports(headers: Mapping) -> bool` — Can this strategy handle this request?
  - [x] `authenticate(headers: Mapping) -> UserContext | None` — Extract user from request.

- [x] **Built-in Strategies**:
  - [x] `SessionCookieAuth` — Browser sessions via cookies.
  - [x] `BearerTokenAuth` — JWT/OAuth2 access tokens.
  - [x] `ApiKeyAuth` — `X-API-Key` header for scripts/integrations.
  - [x] `HeaderAuth` — Federated mode gateway header hydration.
  - [x] `BasicAuth` — HTTP Basic for CLI tools.

- [x] **Strategy Chain** (`AuthChain`):
  - [x] Try each strategy in priority order.
  - [x] First successful match wins.
  - [x] Configurable order via `framework_config.toml` (`create_auth_chain_from_config`).

- [x] **API Key DocType** (Indie Mode):
  - [x] `ApiKey` DocType:
    - [x] `key_hash: str` — Hashed API key (never store plaintext, excluded from serialization).
    - [x] `user_id: str` — Owner of the key.
    - [x] `name: str` — Human-readable label.
    - [x] `scopes: list[str]` — Optional permission scopes.
    - [x] `expires_at: datetime | None` — Expiration.
    - [x] `last_used_at: datetime | None` — Usage tracking.
  - [x] `POST /api/v1/auth/api-keys` — Create new key.
  - [x] `DELETE /api/v1/auth/api-keys/{id}` — Revoke key.
  - [x] `GET /api/v1/auth/api-keys` — List user's keys.

### 1.6 Social Login (OAuth2/OIDC)

> [!IMPORTANT]
> Social login helps Indies avoid password storage and PII liability.

- [x] **Option A: Built-in OAuth2 (Indie)**:
  - [ ] Use `authlib` for OAuth2 flows. *(Full implementation deferred)*
  - [x] Supported Providers (config structure ready):
    - [x] Google (well-known URLs configured)
    - [x] GitHub (well-known URLs configured)
    - [x] Microsoft (well-known URLs configured)
    - [x] Generic OIDC (any compliant provider via `get_oidc_well_known`)
  - [x] Configuration via `framework_config.toml`:
    ```toml
    [auth.oauth]
    enabled = true
    providers = ["google", "github"]

    [auth.oauth.google]
    client_id = "${GOOGLE_CLIENT_ID}"
    client_secret = "${GOOGLE_CLIENT_SECRET}"

    # Generic OIDC provider example
    [auth.oauth.keycloak]
    client_id = "${KEYCLOAK_CLIENT_ID}"
    client_secret = "${KEYCLOAK_CLIENT_SECRET}"
    authorization_url = "https://keycloak.example.com/auth/realms/myrealm/protocol/openid-connect/auth"
    token_url = "https://keycloak.example.com/auth/realms/myrealm/protocol/openid-connect/token"
    userinfo_url = "https://keycloak.example.com/auth/realms/myrealm/protocol/openid-connect/userinfo"
    scope = "openid email profile"
    ```
  - [x] Endpoints:
    - [x] `GET /api/v1/auth/oauth/{provider}/start` — Redirect to provider.
    - [x] `GET /api/v1/auth/oauth/{provider}/callback` — Handle callback. *(Placeholder)*

- [x] **Option B: Federated (Enterprise)**:
  - [x] Delegate to Keycloak / Authelia / Auth0 via `HeaderAuth` strategy.
  - [x] Framework receives headers only (`X-User-ID`, `X-Email`).
  - [x] Zero PII stored locally.

- [x] **SocialAccount DocType**:
  - [x] `provider: str` — OAuth provider name.
  - [x] `provider_user_id: str` — Unique ID from provider.
  - [x] `user_id: str` — Links to local user.
  - [x] `email: str | None` — For lookup (optional).
  - [x] `display_name: str` — For UI display.
  - [x] One User can have multiple SocialAccounts.

- [x] **Passwordless Option** *(Requires Section 10: Email)*:
  - [x] `POST /api/v1/auth/magic-link` — Send email with login link.
  - [x] `GET /api/v1/auth/magic-link/{token}` — Verify and create session.
  - [x] `MagicLinkCredentials` added to identity protocol
  - [x] Token generation with HMAC-SHA256 signing

### 1.7 Session Management

- [x] **Session Storage**:
  - [x] **Redis (Default)**: `RedisSessionAdapter` with JSON storage and TTL.
  - [x] **Database (Fallback)**: `DatabaseSessionAdapter` for indie apps.
  - [x] Configurable via `framework_config.toml`:
    ```toml
    [auth.session]
    backend = "redis"  # or "database"
    ttl_seconds = 86400  # 24 hours
    cookie_name = "session_id"
    cookie_secure = true
    cookie_httponly = true
    cookie_samesite = "lax"
    ```

- [x] **Session DocType** (database backend):
  - [x] `session_id: str` — Unique session identifier.
  - [x] `user_id: str` — Links to user.
  - [x] `expires_at: datetime`
  - [x] `ip_address: str | None`
  - [x] `user_agent: str | None`

- [x] **SessionProtocol Interface**:
  - [x] `create()` — Create new session
  - [x] `get()` — Retrieve by ID
  - [x] `delete()` — Remove session
  - [x] `delete_all_for_user()` — Logout all
  - [x] `list_for_user()` — List active sessions

- [x] **Session API**:
  - [x] `GET /api/v1/auth/sessions` — List active sessions.
  - [x] `DELETE /api/v1/auth/sessions/{id}` — Revoke session.
  - [x] `DELETE /api/v1/auth/sessions` — Logout all.

### 1.8 PII Handling (Indie Guidance)

> [!TIP]
> Store less PII = Less GDPR/compliance burden.

- [x] **Auth Mode Presets** (`AuthMode` enum in `core/pii.py`):
  ```toml
  [auth]
  mode = "social_only"  # No local passwords, minimal PII
  # OR
  mode = "passwordless"  # Email magic links only
  # OR
  mode = "local"  # Traditional username/password
  # OR
  mode = "federated"  # External IdP, zero local PII
  ```

- [x] **Minimal PII Pattern**:
  - [x] `LocalUser` stores only:
    - [x] `email` (for login/notification)
    - [x] `display_name` (for UI)
    - [x] `password_hash` (only if `mode = "local"`)
  - [x] **Do NOT store by default** (`PII_SENSITIVE_FIELDS`):
    - [x] Full legal name
    - [x] Phone number
    - [x] Address
    - [x] Date of birth
  - [x] Helper: `is_sensitive_pii(field_name)` to detect sensitive fields.

- [x] **Data Deletion** (`DeletionMode` enum):
  - [x] `DELETE /api/v1/auth/me` — Delete user account (GDPR right to erasure).
  - [x] `GET /api/v1/auth/me/data` — Export user data (GDPR data portability).
  - [x] Configurable: Hard delete vs anonymize.
    ```toml
    [auth.deletion]
    mode = "hard_delete"  # or "anonymize"
    ```

---

## 2. Tenancy & Attributes (Multi-Tenant Core)

### 2.1 Tenant Protocol
- [x] Create `TenantProtocol` (Interface in `core/interfaces/tenant.py`):
  - [x] `get_current_tenant() -> str`
  - [x] `get_tenant_attributes(tenant_id) -> dict`
  - [x] `TenantContext` model for request context
  - [x] Configuration helpers: `is_multi_tenant()`, `get_default_tenant_id()`

### 2.2 Tenancy Adapters
- [x] **Mode A: Single Tenant (Indie)**:
  - [x] `ImplicitTenantAdapter` (`adapters/tenant.py`):
    - [x] `tenant_id="default"` (configurable).
    - [x] `attributes={"plan": "unlimited", "features": "*"}`.
    - [x] `get_context()` returns `TenantContext` with `is_default=True`.
  - [x] Factory: `create_tenant_adapter_from_headers()` auto-selects.

- [x] **Mode B: Multi-Tenant (Enterprise)**:
  - [x] `HeaderTenantAdapter` (`adapters/tenant.py`):
    - [x] Extracts `X-Tenant-ID` from Gateway headers.
    - [x] Extracts `X-Tenant-Attributes` (JSON) for feature flags, plan.
    - [x] `get_context()` parses headers into `TenantContext`.
    - [x] **Benefit**: Feature toggling per tenant without DB lookup.

### 2.3 Attributes (The New Roles)
- [x] **Goal**: Replace "Roles" with refined "Attributes" (ABAC).
- [x] **Implementation** (in `UserContext`):
  - [x] `user.attributes` (dict): Stores `{"department": "sales", "level": 5, "roles": ["admin"]}`.
  - [x] `user.get_attribute(key, default)` — Get attribute value.
  - [x] `user.has_attribute(key, value)` — Check attribute equality.
  - [x] **Legacy Support**: `user.has_role("x")` checks both `roles` list AND `attributes.roles`.
  - [x] `is_system_user` uses `has_role("System")` for consistency.

---

## 3. Permission Management (Pluggable)

### 3.1 Permission Protocol
- [x] Create `PermissionProtocol` (`core/interfaces/permission.py`):
  - [x] `PolicyEvaluateRequest` - Stateless authorization request
  - [x] `PolicyEvaluateResult` - Result with authorized, decision_source, reason
  - [x] `async def evaluate(request) -> PolicyEvaluateResult`
  - [x] `async def get_permitted_filters(...) -> dict` - RLS support
  - [x] `PermissionAction` enum (read, write, create, delete, submit, cancel, amend)
  - [x] `DecisionSource` enum (rbac, abac, rebac, combo, custom)

### 3.2 Permission Adapters
- [x] **Mode A: Standard (Indie)** - `RbacPermissionAdapter`:
  - [x] Reads permissions from `DocType.Meta.permissions`.
  - [x] Checks `CustomPermission` rules from database.
  - [x] Role matching against `principal_attributes["roles"]`.
  - [x] Admin role bypass (configurable).
  - [x] RLS via `get_permitted_filters()` using `Meta.rls_field`.

- [x] **Mode B: Citadel Policy (Enterprise)** - `CitadelPolicyAdapter`:
  - [x] `AuthorizationRequest` model (Cedar-compatible format):
    - [x] `Principal`: `user.id`
    - [x] `Action`: `doctype:action` (e.g., `Invoice:create`)
    - [x] `Resource`: `doctype:name` (e.g., `Invoice:INV-001`)
    - [x] `TenantID`: `user.tenant_id`
    - [x] `Context`: `doc.as_dict()`
    - [x] `PrincipalAttributes`: `user.attributes` (Diet Claims)
  - [x] `AuthorizationResponse` model with `allowed`, `reason`, `policy_id`
  - [x] Calls configurable policy endpoint (Cedar/OPA)
  - [x] Fallback behavior on error (configurable)
  - [x] Configuration via `[permissions.citadel]` in `framework_config.toml`

---

## 4. File Management

### 4.1 File DocType

- [x] Create `src/framework_m/core/doctypes/file.py`
- [x] Define `File` DocType:
  - [x] `name: str` - File ID (inherited from BaseDocType)
  - [x] `file_name: str` - Original filename
  - [x] `file_url: str` - Storage URL
  - [x] `file_size: int` - Size in bytes
  - [x] `content_type: str` - MIME type
  - [x] `attached_to_doctype: str | None`
  - [x] `attached_to_name: str | None`
  - [x] `is_private: bool` - Private/public file (default: True)

### 4.2 File Upload API

- [x] Create `POST /api/v1/file/upload` (`adapters/web/file_routes.py`):
  - [x] Accept multipart/form-data via `UploadFile`
  - [x] Validate file size (max 10MB default, configurable)
  - [x] Validate file type (configurable whitelist)
  - [x] Generate unique storage path (YYYY/MM/DD/random_filename)
  - [x] Create File DocType record
  - [x] Return file URL and metadata
- [x] Configuration via `[files]` in `framework_config.toml`
- [x] Response models: `FileUploadResponse`, `FileDeleteResponse`

### 4.3 File Download API

- [x] Create `GET /api/v1/file/{file_id}`:
  - [x] Check permissions for private files (RLS on owner)
  - [x] Prepare streaming response from storage
  - [x] Set correct `Content-Type` header
  - [x] Set `Content-Disposition` header (inline/attachment)
  - [x] Support `?inline=true` for browser display
- [x] Create `GET /api/v1/file/{file_id}/info`:
  - [x] Return metadata without downloading content

### 4.4 Storage Adapters

- [x] Implement `LocalStorageAdapter` (`adapters/storage/local.py`):
  - [x] Save files to local directory with atomic writes
  - [x] Organize by date (YYYY/MM/DD) via path parameter
  - [x] Generate unique filenames (handled by file_routes)
  - [x] Path traversal protection
  - [x] Async operations via `aiofiles`

- [x] Implement `S3StorageAdapter` (`adapters/storage/s3.py`):
  - [x] Use `aioboto3` for async S3 operations
  - [x] Presigned URLs for direct client uploads (PUT method)
  - [x] Multipart upload for large files (>25MB)
  - [x] Configure bucket and region from `framework_config.toml`
  - [x] Support S3-compatible services (MinIO, DigitalOcean Spaces)

- [x] `InMemoryStorageAdapter` for testing (`adapters/storage/memory.py`)

---

## 5. Audit Logging (Pluggable)

### 5.1 Audit Log Protocol
- [x] Create `AuditLogProtocol` (`core/interfaces/audit.py`):
  - [x] `AuditEntry` model (user, action, doctype, document_id, changes, metadata)
  - [x] `async def log(...)` - Record audit entry
  - [x] `async def query(...)` - Query with filters/pagination
  - [x] `InMemoryAuditAdapter` for testing

### 5.2 Audit Adapters
- [x] **Mode A: Database (Indie)**:
  - [x] `DatabaseAuditAdapter`: Writes to `ActivityLog` records.
  - [x] Uses ActivityLog DocType from 5.3

- [x] **Mode B: External (Enterprise)**:
  - [x] `FileAuditAdapter`: Writes to `audit.log` (JSONL) for Splunk/Filebeat.
  - [ ] `ElasticAuditAdapter`: Writes directly to Elasticsearch (Phase 10).
  - [x] Prevents main DB bloat.

### 5.3 Activity Log DocType (Indie Only)
- [x] Create `src/framework_m/core/doctypes/activity_log.py`
- [x] Define `ActivityLog` DocType:
  - [x] `user_id: str`
  - [x] `action: str` (create, read, update, delete)
  - [x] `doctype: str`
  - [x] `document_id: str`
  - [x] `timestamp: datetime`
  - [x] `changes: dict | None`
  - [x] `metadata: dict | None`
  - [x] Immutable (no write/delete permissions)

### 5.4 Activity Feed API

- [x] Create `GET /api/v1/activity`:
  - [x] List recent activities
  - [x] Filter by user, doctype, document, action
  - [x] Paginate results (limit, offset)

---

## 6. Error Logging

### 6.1 Error Log DocType

- [x] Create `src/framework_m/core/doctypes/error_log.py`
- [x] Define `ErrorLog` DocType:
  - [x] `title: str` - Short error description
  - [x] `error_type: str` - Exception class name
  - [x] `error_message: str` - Full error message
  - [x] `traceback: str | None` - Full stack trace
  - [x] `request_url: str | None`
  - [x] `user_id: str | None`
  - [x] `request_id: str | None` - For log correlation
  - [x] `timestamp: datetime`
  - [x] `context: dict | None` - Additional context
  - [x] Admin-only permissions (immutable)

### 6.2 Error Logging Integration

- [x] Add global exception handler (`adapters/web/error_handler.py`):
  - [x] Catch all unhandled exceptions
  - [x] Create ErrorLog entry with full context
  - [x] Return user-friendly ErrorResponse (no sensitive data)
  - [x] Log to console in dev mode (configurable)
  - [x] `create_error_handler()` factory for Litestar

---

## 7. Printing & PDF Generation

### 7.1 Print Format DocType

- [x] Create `src/framework_m/core/doctypes/print_format.py`
- [x] Define `PrintFormat` DocType:
  - [x] `name: str` - Display name
  - [x] `doctype: str` - Target DocType
  - [x] `template: str` - Jinja2 template path
  - [x] `is_default: bool`
  - [x] `css: str | None` - Custom CSS
  - [x] `header_html: str | None`, `footer_html: str | None`
  - [x] `page_size: str` (A4, Letter, etc.)
  - [x] `orientation: str` (portrait, landscape)

### 7.2 Jinja Print Adapter

- [x] Create `src/framework_m/adapters/print/jinja_adapter.py`
- [x] Implement `JinjaPrintAdapter`:
  - [x] `async def render_html(doc, template) -> str`
    - [x] Load Jinja2 template
    - [x] Render with document context
    - [x] Return HTML string
  - [x] `async def render_html_string(template_string, doc) -> str`
  - [x] Custom filters: `currency`, `date`, `datetime`

### 7.3 Gotenberg PDF Adapter

- [x] Create `src/framework_m/adapters/print/gotenberg_adapter.py`
- [x] Implement `GotenbergPrintAdapter`:
  - [x] `async def html_to_pdf(html, page_size, orientation, ...) -> bytes`
    - [x] Send HTML to Gotenberg service
    - [x] Return PDF bytes
  - [x] `async def is_available() -> bool` - Health check
- [x] `GotenbergConfig` with URL, timeout, page size, margins
- [x] Page sizes: A4, Letter, Legal, A3, A5

### 7.4 Print API

- [x] Create `GET /api/v1/print/{doctype}/{id}`:
  - [x] Query parameters:
    - [x] `format` - pdf/html (default: pdf)
    - [x] `print_format` - Custom format name
  - [x] Load document (TODO: integrate with repository - phase 10)
  - [x] Check read permission (TODO: integrate with permission - phase 10)
  - [x] Render using JinjaPrintAdapter → GotenbergPrintAdapter
  - [x] Return PDF or HTML with appropriate headers
  - [x] Fallback to HTML if Gotenberg unavailable

---

## 8. System Settings

### 8.1 System Settings DocType

- [x] Create `src/framework_m/core/doctypes/system_settings.py`
- [x] Define `SystemSettings` DocType (singleton):
  - [x] `name: str` - Always "System Settings" (frozen)
  - [x] `app_name: str` - Application display name
  - [x] `timezone: str` - Default timezone
  - [x] `date_format: str` - Date format string
  - [x] `time_format: str` - Time format string
  - [x] `language: str` - Default language code
  - [x] `enable_signup: bool` - Allow registration
  - [x] `session_expiry: int` - Session timeout in minutes
  - [x] `maintenance_mode: bool` - Admin-only access

### 8.2 Settings API

- [x] Create `GET /api/v1/settings`:
  - [x] Return system settings via `SettingsResponse`
  - [x] Cache aggressively (5 min Cache-Control header)
  - [x] `invalidate_settings_cache()` helper
  - [x] `set_settings_cache()` helper

---

## 9. Notification System

### 9.1 Notification DocType

- [x] Create `src/framework_m/core/doctypes/notification.py`
- [x] Define `Notification` DocType:
  - [x] `user_id: str` - Recipient
  - [x] `subject: str`
  - [x] `message: str`
  - [x] `notification_type: str` (info, success, warning, error, etc.)
  - [x] `read: bool`
  - [x] `doctype: str | None` - Related DocType
  - [x] `document_id: str | None`
  - [x] `timestamp: datetime`
  - [x] `from_user: str | None`
  - [x] `metadata: dict | None`
  - [x] RLS on user_id field

### 9.2 Notification API

- [x] Create `GET /api/v1/notifications`:
  - [x] List user's notifications
  - [x] Filter by read/unread
- [x] Create `PATCH /api/v1/notifications/{id}/read` - Mark as read
- [x] Create `DELETE /api/v1/notifications/{id}` - Delete notification

- [x] Create WebSocket endpoint for real-time notifications:
  - [x] `WS /api/v1/notifications/stream` - Real-time notification stream
  - [x] `push_notification(user_id, notification)` - Push to connected clients
  - [x] `create_notification_websocket_router()` factory

---

## 10. Email Integration

### 10.1 Email Queue DocType

- [x] Create `src/framework_m/core/doctypes/email_queue.py`
- [x] Define `EmailQueue` DocType:
  - [x] `to: list[str]` - Recipients
  - [x] `cc/bcc: list[str] | None`
  - [x] `subject: str`
  - [x] `body: str` - HTML body
  - [x] `text_body: str | None` - Plain text alt
  - [x] `status: str` - Queued/Sending/Sent/Failed/Cancelled
  - [x] `priority: str` - low/normal/high
  - [x] `error: str | None`
  - [x] `retry_count/max_retries: int`
  - [x] `queued_at/sent_at: datetime`
  - [x] `reference_doctype/reference_id` - for tracking

- [x] Port-Adapter Pattern:
  - [x] `EmailQueueProtocol` (interface/port)
  - [x] `DatabaseEmailQueueAdapter` (default - uses EmailQueue DocType)
  - [x] `InMemoryEmailQueueAdapter` (testing)
  - [x] `queue_email()` helper function
  - [x] `configure_email_queue()` for swapping adapters
  - [x] Ready for `NotificationServiceAdapter` integration

### 10.2 Email Sender

- [x] Create email sender protocol and adapters:
  - [x] `EmailSenderProtocol` (interface/port)
  - [x] `SMTPEmailSender` - SMTP delivery adapter
  - [x] `LogEmailSender` - Console logging (development)
  - [x] `SMTPConfig` - Configuration dataclass

- [x] Create background job `EmailProcessor`:
  - [x] `process_queue()` - Process pending emails
  - [x] `process_single(queue_id)` - Process single email
  - [x] Update EmailQueue status
  - [x] Retry logic (placeholder)

- [x] Add helper function:
  - [x] `queue_email()` - Queue for sending
  - [x] `send_queued_email()` - Process single email

---

## 11. Testing

### 11.1 Unit Tests

- [x] Test User DocType validation (`core/doctypes/test_user.py`, `core/services/test_user_manager.py`)
- [x] Test Role assignment (`adapters/auth/test_rbac_permission.py`)
- [x] Test Permission checking (8 permission test files)
- [x] Test File upload/download (`core/test_file.py`, `adapters/web/test_file_routes.py`)
- [x] Test Print rendering (`core/test_print_format.py`, `adapters/print/test_*.py`)

### 11.2 Integration Tests

- [x] Test user creation and login (`integration/test_api_endpoints.py`)
- [x] Test permission enforcement (`core/test_object_level_permissions.py`)
- [x] Test file storage (local and S3) (`adapters/storage/test_local.py`, `adapters/storage/test_s3.py`)
- [x] Test PDF generation with Gotenberg (`adapters/print/test_gotenberg_adapter.py`)
- [x] Test email sending (`adapters/email/test_sender_processor.py`)

---

## Validation Checklist

Before moving to Phase 07, verify:

- [x] User and Role management works
- [x] Permissions are enforced correctly
- [x] Files can be uploaded and downloaded
- [x] Audit logs are created
- [x] PDFs can be generated
- [x] Emails can be sent

---

## Anti-Patterns to Avoid

❌ **Don't**: Store files in database
✅ **Do**: Use storage adapter (local/S3)

❌ **Don't**: Generate PDFs in main process
✅ **Do**: Use microservice (Gotenberg)

❌ **Don't**: Send emails synchronously
✅ **Do**: Queue emails as background jobs

❌ **Don't**: Hardcode system settings
✅ **Do**: Store in SystemSettings DocType


Document: checklists/phase-09b-docs.md

# Phase 09B: Documentation

**Objective**: Create comprehensive documentation covering fundamentals, migration guides, i18n, multi-tenancy usage, and API references.

> [!IMPORTANT]
> **Revisit with Real Code**: The `docs/notes/fundamentals.md` and `docs/notes/migration-from-frappe.md` currently show planned API designs. Before completing this phase, all examples must be validated with real, working code—actual Container initialization, DI wiring with `dependency-injector`, entrypoint overrides, and tested implementations.

---

## 1. Documentation (Basic Setup)

> [!NOTE]
> For comprehensive LLM-ready documentation, auto-generated API reference, screenshot automation,
> and machine-readable exports, see **[Phase 12: LLM-Ready Documentation](./phase-12-llm-documentation.md)**.

### 1.1 Basic User Documentation ✅

- [x] Create `docs/user/` directory
- [x] Write essential guides:
  - [x] Getting started (`docs/user/getting-started.md`)
  - [x] Creating DocTypes (`docs/user/creating-doctypes.md`)
  - [x] Using the Desk (`docs/user/using-the-desk.md`)
- [x] **Auto-generated docs** (`scripts/generate_docs.py`):
  - [x] `docs/user/_generated/field-types.md`
  - [x] `docs/user/_generated/api-endpoints.md`
- [x] **Truthfulness tests** (`tests/docs/test_user_docs.py` - 17 tests passing)

### 1.2 Basic Developer Documentation ✅

- [x] Create `docs/developer/` directory
- [x] Write essential guides:
  - [x] Architecture overview (`docs/developer/architecture.md`)
  - [x] Creating custom apps (`docs/developer/creating-apps.md`)
  - [x] Defining DocTypes (`docs/developer/defining-doctypes.md`)
- [x] **Auto-generated docs** (`scripts/generate_docs.py`):
  - [x] `docs/developer/_generated/protocols.md` (20 protocols)
  - [x] `docs/developer/_generated/hooks.md` (10 hooks)
  - [x] `docs/developer/_generated/cli-reference.md`
- [x] **Truthfulness tests** (`tests/docs/test_developer_docs.py` - 16 tests passing)

### 1.3 Framework Fundamentals ✅

> Already exists at `docs/notes/fundamentals.md`

- [x] Minimal core principle (irreplaceable: async Python, DI, protocols, entrypoints)
- [x] DI as universal composer
- [x] Bootstrap flow
- [x] Layer composition
- [x] Adding custom adapters
- [x] Testing with DI

### 1.4 Migration Guide (Frappe → Framework M) ✅

> Already exists at `docs/notes/migration-from-frappe.md`

- [x] Cover backend patterns
- [x] Cover frontend patterns
- [x] Practical examples

### 1.5 API Documentation ✅

> Already configured in `adapters/web/app.py`

- [x] Litestar auto-generates OpenAPI spec (`/schema`)
- [x] Swagger UI (`/schema/swagger`)
- [x] ReDoc (`/schema/redoc`)
- [x] **Truthfulness tests** (`test_developer_docs.py::TestAPIDocumentation` - 3 tests)

### 1.6 Example Apps ✅

- [x] Create minimal example apps:
  - [x] Simple TODO app (`docs/examples/todo/`)
  - [x] Basic CRM (`docs/examples/crm/`)
- [x] Example index (`docs/examples/README.md`)

---

## 2. Internationalization (i18n)

> [!NOTE]
> This section includes both **backend implementation** (I18nProtocol, adapters) and **frontend integration**. Implementation happens in this phase alongside documentation.

### 2.1 Backend i18n ✅

> Already implemented - see `docs/developer/i18n.md`

- [x] Create `I18nProtocol` interface (`core/interfaces/i18n.py`):
  - [x] `translate(text, locale, context, default) -> str`
  - [x] `get_locale() -> str`
  - [x] `set_locale(locale) -> None`
  - [x] `get_available_locales() -> list[str]`

- [x] Create `InMemoryI18nAdapter` (`adapters/i18n/__init__.py`):
  - [x] Dict-based translation storage
  - [x] Add translations dynamically
  - [x] String interpolation with context

- [x] Create `Translation` DocType:
  - [x] `source_text: str` (original)
  - [x] `translated_text: str`
  - [x] `locale: str` (e.g. "hi", "ta", "en")
  - [x] `context: str | None` (e.g. "button", "label")
  - [x] Unique constraint: `(source_text, locale, context)`

- [x] Create `DefaultI18nAdapter`:
  - [x] Load translations from DB on startup
  - [x] Cache in memory (Redis optional)
  - [x] Fallback chain: request locale → default locale → source text

- [x] Locale resolution order:
  - [x] 1. `Accept-Language` header
  - [x] 2. User preference (`user.locale`)
  - [x] 3. Tenant default (`tenant.attributes.default_locale`)
  - [x] 4. System default (`settings.DEFAULT_LOCALE`)

- [x] DocType field labels:
  - [x] `Field.label` supports i18n key
  - [x] Meta API returns translated labels based on request locale

### 2.2 Frontend i18n

- [x] Install `react-i18next`:
  - [x] Configure with backend translations endpoint
  - [x] Lazy load locale bundles

- [x] Translation workflow:
  - [x] Extract strings from components (`i18next-parser`)
  - [x] Upload to Translation DocType
  - [x] Download merged translations as JSON

- [x] Components:
  - [x] `useTranslation()` hook in all components
  - [x] `<Trans>` for interpolated strings
  - [x] Locale switcher in navbar

### 2.3 Per-Tenant Locales

- [x] Tenant can override default locale
- [x] Tenant can provide custom translations (override system)
- [x] Tenant locale from `TenantContext.default_locale`

---

## 3. Multi-Tenancy Usage & Documentation

> [!NOTE]
> TenantProtocol and adapters are implemented in Phase 06. This section covers **usage documentation** and **frontend integration**.

### 3.1 Tenancy Modes Documentation ✅

- [x] Document Mode A (Single Tenant / Indie):
  - [x] `ImplicitTenantAdapter` usage
  - [x] Default tenant ID configuration
  - [x] When to use (standalone deployments)

- [x] Document Mode B (Multi-Tenant / Enterprise):
  - [x] `HeaderTenantAdapter` usage
  - [x] Gateway configuration (X-Tenant-ID, X-Tenant-Attributes)
  - [x] Citadel IAM integration for tenant extraction

- [x] Document tenant isolation patterns:
  - [x] Logical isolation (tenant_id column, RLS)
  - [x] Database-per-tenant (db_binds per tenant)
  - [x] Schema-per-tenant (PostgreSQL schemas)

### 3.2 Using TenantContext in Code ✅

> Documented in `docs/developer/tenancy-modes.md` - "Using TenantContext in Code" section

- [x] Document injection pattern:
  - [x] Dependency injection via `request.state.tenant`
  - [x] Feature flags from tenant attributes
  - [x] Example endpoints using TenantContext

- [x] Document tenant-aware queries:
  - [x] Automatic RLS filtering with GenericRepository
  - [x] Manual tenant_id filtering with FilterSpec
  - [x] Cross-tenant queries (admin only)

### 3.3 Frontend Multi-Tenancy ✅

> Documented in `docs/developer/tenancy-modes.md` - "Frontend Multi-Tenancy" section

- [x] Tenant context in frontend:
  - [x] Extract from JWT or API response
  - [x] Store in React context (TenantProvider, useTenant hook)
  - [x] Pass tenant_id in API requests (if not in headers)

- [x] Tenant-specific UI:
  - [x] Tenant branding (logo, colors) - useTenantBranding hook
  - [x] Feature flags from `tenant.attributes` - FeatureGate component
  - [x] Tenant selector (admin UI) - TenantSelector component

### 3.4 Switching Adapters ✅

> Documented in `docs/developer/tenancy-modes.md` - "Switching Adapters" section

- [x] Document adapter selection:
  - [x] Configuration via `framework_config.toml` (mode = "single" | "multi")
  - [x] Factory function `create_tenant_adapter_from_headers()`
  - [x] Entrypoint registration for custom adapters
  - [x] Testing with MockTenantAdapter

---

## 4. Theming ✅

> Implemented in frontend - see `docs/developer/theming.md`

- [x] Add theme support:
  - [x] Light/dark mode with ThemeContext and useTheme hook
  - [x] Custom color schemes via CSS variables (`:root` and `.dark`)
  - [x] Store user preference in localStorage with system detection fallback
  - [x] ThemeToggle component integrated in Navbar

---

## 5. Testing

### 5.1 Frontend Tests ✅

> Complete testing infrastructure - see `docs/developer/frontend-testing.md`

- [x] Unit tests for components:
  - [x] Test `AutoForm` rendering (12 tests)
  - [x] Test `AutoTable` rendering (11 tests)
  - [x] Test `ThemeToggle` functionality (6 tests)
  - [x] Test form validation

- [x] Integration tests:
  - [x] Test full CRUD flow (3 tests)
  - [x] Test theme persistence (7 tests)
  - [x] Test navigation
  - [x] Test real-time updates

- [x] E2E tests with Playwright:
  - [x] Test user login workflows (7 tests)
  - [x] Test create record workflows (9 tests)
  - [x] Test cross-browser compatibility (10 tests)

- [x] Test infrastructure:
  - [x] Vitest setup with happy-dom
  - [x] Testing Library integration
  - [x] Playwright configuration
  - [x] CI/CD test scripts
  - [x] Coverage reporting

---

## Validation Checklist

Before moving to Phase 10, verify:

- [x] Frontend can render any DocType
- [x] Forms are generated from metadata
- [x] Tables support sorting and filtering
- [x] Real-time updates work
- [x] Documentation is comprehensive
- [x] Example apps demonstrate features

---

## Anti-Patterns to Avoid

❌ **Don't**: Hardcode DocType-specific UI
✅ **Do**: Generate UI from metadata

❌ **Don't**: Build separate forms for each DocType
✅ **Do**: Use generic `AutoForm` component

❌ **Don't**: Skip documentation
✅ **Do**: Document as you build

❌ **Don't**: Ignore accessibility
✅ **Do**: Follow WCAG guidelines


Document: checklists/phase-11-package-split.md

# Phase 11: Package Split & MX Pattern

**Objective**: Enable enterprises to build custom "MX" variants (e.g., MongoDB-primary, Cassandra-primary) without forking, via pure package composition.

> [!NOTE]
> This phase should only be started AFTER Phases 1-10 are stable.
> The split is a refactor, not a feature addition.

---

## 1. Package Split Strategy

### 1.1 Split into Core + Standard

- [x] Create `framework-m-core` package:
  - [x] All Protocols (Repository, Permission, EventBus, Cache, etc.)
  - [x] DI Container setup (`dependency-injector`)
  - [x] BaseDocTypeProtocol (not concrete class)
  - [x] Controller/Hook system
  - [x] CLI framework (`cyclopts` base)
  - [x] **Zero adapters, zero concrete implementations**

- [x] Create `framework-m-standard` package:
  - [x] Depends on `framework-m-core`
  - [x] SQLAlchemy adapters (SchemaMapper, GenericRepository, UnitOfWork)
  - [x] RBAC PermissionAdapter
  - [x] Redis EventBusAdapter
  - [x] Arq JobQueueAdapter
  - [x] Default Bootstrap sequence

- [x] Create `framework-m` meta-package:
  - [x] Depends on both `framework-m-core` + `framework-m-standard`
  - [x] "Batteries included" install for Indie devs

### 1.2 Decouple Development Tooling

- [x] Move scaffolding and codegen from `framework-m` to `framework-m-studio`:
  - [x] Move `new:app` command and templates
  - [x] Move `new:doctype` command and templates
  - [x] Move `new:site` (placeholder) to Studio
  - [x] Move all Jinja2-based scaffolding code
  - [x] Goal: `framework-m` (Core/Standard) should have **zero** dependencies on Jinja2 or LibCST.
  - [x] Result: `framework-m` is optimized for production containers, while `framework-m-studio` remains the developer's workbench.

---

## 2. Protocols to Extract to Core

### 2.1 Ensure All Protocols are Pure

- [x] `RepositoryProtocol` — no SQLAlchemy types
- [x] `UnitOfWorkProtocol` — generic session interface
- [x] `SchemaMapperProtocol` — Pydantic → Storage mapping
- [x] `BootstrapProtocol` — Startup step interface
- [x] Verify: No imports from `sqlalchemy` in `framework-m-core`

### 2.2 BaseDocType as Protocol

- [x] Create `BaseDocTypeProtocol`:
  ```python
  class BaseDocTypeProtocol(Protocol):
      id: str
      name: str
      created_at: datetime
      modified_at: datetime
  ```
- [x] `framework-m-standard` provides `BaseDocType(BaseDocTypeProtocol)` with `owner`, `_version`, etc.
- [x] MX packages can provide their own BaseDocType

---

## 3. Bootstrap via Entry Points

### 3.1 Define Bootstrap Plugin System

- [x] Create entry point group: `framework_m.bootstrap`
- [x] Define `BootstrapStep` protocol:
  ```python
  class BootstrapStep(Protocol):
      name: str
      order: int  # Lower runs first
      async def run(self, container: Container) -> None: ...
  ```
- [x] Discover and sort steps from entry points
- [x] Allow MX packages to override steps by name

### 3.2 Default Bootstrap Steps (framework-m-standard)

- [x] `init_engine` (order=10) — Create SQLAlchemy engine
- [x] `init_registries` (order=20) — MetaRegistry, FieldRegistry
- [x] `sync_schema` (order=30) — Run SchemaMapper on all DocTypes
- [x] `init_adapters` (order=40) — Bind adapters to DI container

---

## 4. Adapter Auto-Discovery

### 4.1 Entry Point Groups for Adapters

- [x] `framework_m.adapters.repository` — GenericRepository implementation
- [x] `framework_m.adapters.schema_mapper` — SchemaMapper implementation
- [x] `framework_m.adapters.unit_of_work` — UnitOfWork implementation
- [x] `framework_m.adapters.permission` — PermissionAdapter
- [x] `framework_m.adapters.event_bus` — EventBusAdapter

### 4.2 DI Auto-Binding

- [x] On startup, scan entry points
- [x] Bind discovered adapters to Protocols in DI container
- [x] Priority: Explicit config > Entry point > Default

---

## 5. Example: framework-mx-mongo

### 5.1 Package Structure

- [x] Create framework-mx-mongo package with proper structure
- [x] Implement MongoSchemaMapper (Pydantic → MongoDB collections)
- [x] Implement MongoGenericRepository (CRUD via Motor)
- [x] Implement MongoSessionFactory (MongoDB sessions & transactions)
- [x] Implement MongoInit bootstrap step

### 5.2 Entry Point Registration

- [x] Register repository adapter entry point
- [x] Register schema_mapper adapter entry point
- [x] Register unit_of_work adapter entry point
- [x] Register bootstrap entry point

### 5.3 Testing

- [x] Test protocol compliance for all adapters
- [x] Test entry point registration and discovery
- [x] Test MX pattern override scenarios
- [x] Verify 20/20 tests passing

---

## 6. Documentation

- [x] Architecture guide: "MX Pattern ADR" (`docs/adr/0006-mx-pattern.md`)
- [x] Tutorial: "MongoDB-Primary Framework MX" (`docs/tutorials/mongodb-mx-tutorial.md`)
- [x] Reference: All protocol definitions (`docs/reference/protocol-reference.md`)

---

## 7. Testing

- [x] Test: `framework-m-core` installs without SQLAlchemy
- [x] Test: `framework-m-standard` adds SQLAlchemy adapters
- [x] Test: MX package can override adapters
- [x] Test: Coverage configuration includes all workspace packages
- [x] Test: Bootstrap order respects priority

---

## Migration Notes

> [!WARNING]
> This phase involves significant refactoring:
>
> - All imports must be updated to use `framework_m_core` or `framework_m_standard`
> - Tests need updating for split packages
> - CI needs to test all three packages

**Recommended approach:**

1. Create `core` and `standard` folders inside existing `framework-m`
2. Move code incrementally
3. Create separate packages only after internal split is stable


Document: checklists/phase-05-cli-tools.md

# Phase 05: CLI & Developer Tools

**Objective**: Build a comprehensive CLI tool (`m`) to replace Frappe's "bench", with commands for development, deployment, and management.

---

## 1. CLI Framework Setup

- [x] Create `src/framework_m/cli/main.py`
- [x] Use `cyclopts` for CLI framework (native async, better type support)
- [x] Setup main CLI app:

  ```python
  app = cyclopts.App(name="m", help="Framework M CLI")
  ```

- [x] Add version command:

  ```bash
  m --version
  ```

- [x] Add help documentation for all commands

### 1.1 Pluggable CLI Architecture (Entry Points)

- [x] Define Entry Point Group: `framework_m.cli_commands`
- [x] Implement Plugin Loader in `plugin_loader.py`:
  - [x] Use `importlib.metadata.entry_points(group="framework_m.cli_commands")`
  - [x] Iterate over registered entry points
  - [x] If entry point is a `cyclopts.App`: `app.command(plugin_app, name=ep.name)`
  - [x] If entry point is a function: `app.command(plugin_func, name=ep.name)`
- [x] Goal: 3rd party apps (and `framework-m-studio`) can add commands to `m` without modifying core.
- [x] **Customization Philosophy**:
  - [x] Standard commands (`start`, `test`) are strict relays.
  - [x] **For customization** (e.g., `start-mqtt`, `super-test`): Users MUST register a custom command via entry points.
  - [x] **Result**: `m` remains a clean, standard runner.

---

## 2. Development Server (Uvicorn Relay)

### 2.1 Start Command

- [x] Implement `m prod`:
  - [x] **Implementation**: Wrapper around `uvicorn`.
  - [x] **Argument Forwarding**: Uses cyclopts Parameter for options.
  - [x] **Usage**: `m prod --workers 4 --log-level debug`
  - [x] **Value Add**: Sets `PYTHONPATH`, auto-detects app, transparently passes args.

### 2.2 Worker Command (Taskiq Relay)

- [x] Implement `m worker`:
  - [x] **Implementation**: Native Taskiq integration (better than wrapper).
  - [x] **Options**: `--concurrency`, `--verbose`.
  - [x] **Usage**: `m worker --concurrency 8`

### 2.3 Studio Command (Litestar Relay)

- [x] Implement `m studio`:
  - [x] **Implementation**: Wrapper around `uvicorn` for Studio app.
  - [x] **Options**: `--host`, `--port`, `--reload`, `--log-level`.
  - [x] **Usage**: `m studio --port 9000 --reload`

---

## 3. Database Commands (Alembic Relay)

### 3.1 Migration Commands

- [x] Implement `m migrate`:
  - [x] **Implementation**: Wrapper around MigrationManager (uses Alembic).
  - [x] **Commands**: `migrate`, `create`, `rollback`, `status`, `history`, `init`.
  - [x] **Usage**: `m migrate create "add users" --autogenerate`
  - [x] **Value Add**: Auto-detects schema changes, supports custom DB URL, env vars.

---

## 4. Scaffolding (Cruft/Cookiecutter Relay)

### 4.1 New App (Starter Template)

- [x] Implement `m new:app`:
  - [x] **Implementation**: Wrapper around `cruft` (cookiecutter).
  - [x] **Options**: `--template`, `--checkout`, `--no-input`.
  - [x] **Usage**: `m new:app myapp --checkout main`

### 4.2 New DocType (Jinja Relay)

- [x] Implement `m new:doctype <name>`:
  - [x] **Implementation**: Template rendering (embedded templates).
  - [x] **Output**: `doctype.py`, `controller.py`, `test_*.py`, `__init__.py`.
  - [x] **App Detection**: `--app` > pyproject.toml > interactive prompt.

#### App Detection Strategy

- [x] **CLI Parameter (Unattended/Automation)**:
  - [x] `--app <app_name>`: Explicit app target.
  - [x] If `--app` provided, use it directly (no prompt).
  - [x] **Automation Friendly**: Scripts can pass `--app` to avoid prompts.

- [x] **Auto-Detection from CWD**:
  - [x] If `--app` not provided, attempt to detect from current directory.
  - [x] Check if CWD is inside an app's source tree.
  - [x] Look for `pyproject.toml` in CWD or parent directories.
  - [x] Read `[project] name` to determine app.

- [x] **Interactive Prompt (Default Fallback)**:
  - [x] If detection fails AND stdin is interactive (TTY):
    - [x] List installed apps from `framework_m.apps` entry points.
    - [x] Prompt user to select: `"Which app? [my_app, other_app]"`.
  - [x] If stdin is NOT interactive (pipe/automation):
    - [x] **Fail with clear error**: `"Cannot detect app. Use --app <name>."`.
    - [x] Exit code 1 (enables CI/automation to catch failures).

- [x] **Implementation**: check `require_app()` in `new.py`

- [x] **Scaffold Output Location**:
  - [x] Files created in: `{cwd}/doctypes/{doctype_name}/`
  - [x] Structure: `__init__.py`, `doctype.py`, `controller.py`, `test_*.py`

- [x] **Templates**:
  - [x] Embedded templates in `new.py` (no external Jinja2 files needed).

---

## 5. Testing & Quality (Standard Tool Relays)

### 5.1 Test Relay

- [x] Implement `m test`:
  - [x] **Command**: `pytest`
  - [x] **Options**: `--verbose`, `--coverage`, `-k`.
  - [x] **Usage**: `m test -k "user" --verbose`
  - [x] **Value Add**: Configures `PYTHONPATH`.

### 5.2 Lint/Format Relay

- [x] Implement `m lint`:
  - [x] **Command**: `ruff check . --fix`
- [x] Implement `m format`:
  - [x] **Command**: `ruff format .`
- [x] Implement `m typecheck`:
  - [x] **Command**: `mypy .`

---

## 6. Configuration Management

### 6.1 Config File

- [x] Create `framework_config.toml` structure:
  - [x] Supports `[framework]`, `[apps]` sections
  - [x] Load/save with TOML format
  - [x] Auto-detect in CWD or parent directories

### 6.2 Config Commands

- [x] Implement `m config:show`: Display current config.
- [x] Implement `m config:set <key> <value>`: Update config.

---

## 7. Utility Relays

### 7.1 Console (IPython Relay)

- [x] Implement `m console`:
  - [x] **Implementation**: Wrapper around `ipython` or `python -m asyncio`.
  - [x] **Value Add**: Pre-imports framework, async support.
  - [x] **Options**: `--no-ipython` for plain Python.

### 7.2 System Info

- [x] Implement `m info`:
  - [x] Show versions (Framework, Python).
  - [x] `--verbose` for platform and service info.

### 7.3 Routes

- [x] Implement `m routes`:
  - [x] Points to OpenAPI/Swagger docs.

---

## 8. Build Commands

### 8.1 Frontend Build

> [!NOTE]
> Frontend build logic (`m build`) is defined in **Phase 09: Frontend & Docs**.
> The CLI command is registered here but implementation details are in Phase 09.

- [x] Implement `m build` (placeholder):
  - [x] Relay to frontend build system (see Phase 09).
  - [x] Detects package.json and runs `npm run build`.

### 8.2 Docker Build

- [x] Implement `m build:docker`:
  - [x] **Command**: Wrapper around `docker build`.
  - [x] **Value Add**: Reads image name/tag from `framework_config.toml`.
  - [x] **Options**: `--tag`, `--file`, `--no-cache`, `--push`.

---

## 9. Pluggable Extensions

- These commands are **NOT** in `framework-m`.
- Provided by `framework-m-studio` via entry points:
  - [x] Plugin loader implemented (`load_plugins()`)
  - [ ] `m studio` (external)
  - [ ] `m docs:generate` (external)
  - [ ] `m codegen` (external)

---

## 10. CLI Entry Point

- [x] Configure `pyproject.toml` scripts: `m = "framework_m.cli.main:app"`
- [x] Test installation (`m --help`) - 22 commands registered.