# VNCmail+ v1.7.8 → v1.8.0 Development Plan **Date:** 2026-08-07 **Target:** `brvncde-dotcom/vncmail-plus` (Next.js 16) **Baseline:** VNCmailgraph audit (82 findings) + Bugs VNCmail+.docx (12 missing features) --- ## Phase Structure | Phase | Focus | Features + Fixes | Autonomy | |-------|-------|-----------------|----------| | **P1** | Critical fixes | 8 CRITICAL + 10 HIGH audit findings | Full autonomous | | **P2** | Missing features | 12 docx features, code reuse from Angular | Full autonomous (board decided VNCtalk/Collabora/ActionWheel) | | **P3** | Security hardening | Auth, cookies, rate limiting, encryption | Full autonomous | | **P4** | Polish & sync | Remaining HIGH + cross-feature substrate | Full autonomous | --- ## Phase 1 — Critical Bug Fixes (Autonomous) ### P1.1 — Error Swallowing (CRITICAL C1) **Issue:** `getEmails`/`searchEmails` return empty on transport failure. Network-down = empty folder. **Fix:** Sample `transportHealth().transportFailureCount()` before/after JMAP reads. If incremented, throw `TransportError` instead of returning `{ emails: [], ... }`. Store catches and shows connectivity banner. - Files: `lib/jmap/client.ts`, `stores/email-store.ts`, `lib/jmap/transport-health.ts` - Reuse: None - Effort: 2h ### P1.2 — FTS5 Schema-Drop Rebuild (CRITICAL C6) **Issue:** Schema version bump drops all tables without auto-rebuild. **Fix:** After `DROP TABLE IF EXISTS`, trigger automatic `catchUpAll()` in the same operation. Add user-facing "Rebuilding search index..." indicator. - Files: `lib/mail-index/store.ts`, `lib/mail-index/reindex.ts` - Reuse: None - Effort: 2h ### P1.3 — Auth Credentials in localStorage (CRITICAL C5) **Issue:** `auth-storage` + `account-storage` contain server URLs and usernames in plaintext. **Fix:** Encrypt the Zustand persist payload for these two stores using a key derived from session secret or a per-device key. Scope: `auth-store.ts` and `account-store.ts` persist middleware. - Files: `stores/auth-store.ts`, `stores/account-store.ts`, new `lib/auth/local-storage-crypto.ts` - Reuse: AES-256-GCM pattern from `lib/auth/crypto.ts` - Effort: 4h ### P1.4 — Settings Lock Bypass (CRITICAL C7) **Issue:** `updateSetting()` has no policy check. Admin locks bypassable via store. **Fix:** Gate `updateSetting(key, value)` with `isSettingLocked(key)`. Add `{ force: true }` opt-in for legitimate bypassers (auth bootstrap, settings sync). Audit all `updateSetting` call sites. - Files: `stores/settings-store.ts`, `stores/auth-store.ts`, `lib/settings-sync.ts` - Reuse: None - Effort: 3h ### P1.5 — Offline Push Detection (CRITICAL C8) **Issue:** No `offline` event listener. Transports retry blindly, draining battery. **Fix:** Add `window.addEventListener('offline', ...)` that pauses all push transports. Add `navigator.onLine` gate on each transport cycle. Wire `transportHealth().likelyOffline` into push lifecycle. - Files: `lib/jmap/client.ts` - Reuse: None - Effort: 1h ### P1.6 — Admin Session Revocation (CRITICAL C4) **Issue:** AES-256-GCM token has no server-side revocation. **Fix:** Add in-memory token blacklist (Set with TTL) in admin session middleware. Logout adds token `jti` to blacklist. Cleanup expired entries on verification. - Files: `lib/admin/session.ts`, `app/api/admin/auth/route.ts` - Reuse: None - Effort: 3h ### P1.7 — Calendar ID Collision (CRITICAL C2) **Issue:** Recurrence expansion (`:` delimiter) collides with shared event prefix. **Fix:** Change expansion delimiter from `:` to `::occurrence::`. Update `stripLocalAccountPrefix` and all ID parsing. - Files: `lib/recurrence-expansion.ts`, `stores/calendar-store.ts` - Reuse: None - Effort: 1h ### P1.8 — Calendar Cross-Account Dedup (CRITICAL C3) **Issue:** Multi-account event aggregation has no UID dedup. **Fix:** After `Promise.all(...).flat()`, run `uniqueBy(events, e => e.uid + e.recurrenceId)` pass. - Files: `stores/calendar-store.ts` - Reuse: None - Effort: 1h ### P1.9 — HIGH Fixes Batch - **H4:** Strip `access_token` from OAuth error logs (`lib/oauth/token-exchange.ts`) — 30min - **H9:** Fix bcrypt in `isHashed()` (`lib/admin/password.ts`) — 1h - **H18:** Default `autoSelectReplyIdentity` to `true` (`stores/settings-store.ts`) — 30min - **H7:** DOMPurify HTML template body on import (`lib/template-utils.ts`) — 1h - **H13:** `calendarTasksEnabled` runtime enforcement (`stores/task-store.ts`, `components/calendar/`) — 2h - **H14:** try/catch + toast on task mutations (`stores/task-store.ts`) — 1h - **H8:** Derive `secure` cookie from `x-forwarded-proto` (`lib/admin/session.ts`) — 1h - **H3:** Rate-limit user auth endpoints (`app/api/auth/*`) — 3h - **H2:** Await state snapshot in `reconcileAfterWebSocketFallback` (`lib/jmap/client.ts`) — 2h - **H1:** Push handler: add ContactCard/FileNode branches (`stores/email-store.ts`) — 3h **P1 Total:** ~32h --- ## Phase 2 — Missing Features (Autonomous, Code Reuse) ### P2.1 — Extended Signatures (Multiple per Identity + HTML Editor) **Docx:** "Add option to create various email signatures and to select standard signature for new emails and for replies" **Reuse from Angular:** - Models: `signature.model.ts` (Signature interface), `identity.model.ts` (zimbraPrefDefaultSignatureId) - API: Signature CRUD endpoints, `modifySignaturePrefs()` pattern - UI logic: Quill editor toolbar config → `react-quill-new` equivalent **Implementation:** 1. New `stores/signature-store.ts` — Signature[] with CRUD + identity assignment 2. New `components/settings/signature-settings.tsx` — list management page 3. New `components/settings/signature-editor-modal.tsx` — Quill-based HTML editor 4. Extend `components/identity/identity-form.tsx` — default/forward-reply signature picker 5. Extend `components/email/email-composer.tsx` — signature selector dropdown in compose 6. JMAP/Sieve integration: if the server stores signatures as Sieve or Identity properties, map accordingly **Effort:** 12h ### P2.2 — Create Appointment from Email **Docx:** "create a calendar entry from an email with recipients as participants and email text in description" **Reuse from Angular:** - `Appointment` interface from `appoinment.model.ts` - Calendar compose pre-fill logic from `calendar-compose.component.ts` - API payload shapes for event creation **Implementation:** 1. New `components/email/create-appointment-button.tsx` — button in email toolbar 2. Extend `components/calendar/event-modal.tsx` — accept pre-fill props (title=subject, description=body, participants=from+to+cc) 3. Wire "Create Appointment" action into email-viewer toolbar + context menu **Effort:** 6h ### P2.3 — Folder Sharing (Mail, Calendar, Contacts, Files) **Docx:** "Share Folder feature" + "Sharing to see all folders shared by me and with me" **Reuse from Angular:** - `ShareFolderComponent` (564 lines) — full sharing dialog with email autocomplete, role selection - `PreferencesSharingComponent` (556 lines) — "shared by me" / "shared with me" views - `AllSharingFoldersDialogComponent` — tree-based folder browser - API: share/revoke/accept/decline endpoints and payload shapes **Implementation:** 1. New `components/sharing/share-folder-dialog.tsx` — modal with email autocomplete, role picker (read/read-write/admin), message 2. New `components/sharing/share-folder-revoke-dialog.tsx` — revoke confirmation 3. New `components/sharing/accept-share-dialog.tsx` — accept incoming share 4. New `components/settings/sharing-settings.tsx` — "Shared by me" / "Shared with me" tabs with folder tree 5. Extend folder context menus in email sidebar, calendar sidebar, contacts sidebar, files sidebar with "Share Folder..." action 6. New `stores/sharing-store.ts` — tracking shares state 7. API routes: `app/api/sharing/*` — share/revoke/accept/decline/find **Effort:** 18h ### P2.4 — Calendar Dashlet (Mini Calendar in Mail View) **Docx:** "Show Calendar in a dashlet in the bottom left corner" **Reuse from Angular:** - `sidebar-mini-calendar.component.ts` (701 lines) — month grid, swipe navigation, tooltip - Tooltip directive logic for fetching day events **Implementation:** 1. New `components/calendar/mini-calendar-dashlet.tsx` — compact month grid widget 2. Integrate into mail sidebar or bottom-left overlay in the mail page layout 3. Show date dots for days with events, today highlight, click to navigate to calendar 4. Optionally: toggleable via user setting "Show calendar dashlet" **Effort:** 8h ### P2.5 — Email Import (.eml, tgz, zip) **Docx:** "Missing: feature to import emails" **Reuse from Angular:** - `ImportExportComponent` (513 lines) — import types, destination folder, resolve settings - `preferenceService.importFromFile()` — API request shape - CSV type auto-detection logic **Implementation:** 1. Extend existing `.eml` import (already partial — see `lib/eml-import.ts` and `components/email/` for `.eml` preview) 2. Add ZIP/TGZ/TAR archive import — extract, iterate, import each `.eml` 3. New `components/settings/import-settings.tsx` — import UI with file picker, destination folder, conflict resolution 4. API route: extend `app/api/account/*` or new `app/api/import/*` **Effort:** 10h ### P2.6 — Contact Import (vCard/CSV) **Docx:** "Missing: feature to import contacts" **Reuse from Angular:** - `contact-file-import-dialog.component.ts` (228 lines) — CSV upload, folder selector - `contactService.importContacts()` — API endpoint - vCard parsing: `lib/vcard.ts` already exists in codebase **Implementation:** 1. Extend existing `components/contacts/contact-import-dialog.tsx` to support CSV (currently vCard-only) 2. Add CSV column mapping UI (map CSV columns to contact fields) 3. Folder selection for import destination 4. Dedup handling **Effort:** 6h ### P2.7 — Free/Busy View **Docx:** "Missing: free/busy view" **Reuse from Angular:** - `scheduler.component.ts` (1303 lines) — full free/busy grid - `scheduler-utils.ts` — free-busy status constants (`fba` values, `FBA_TO_PTST` mapping) - `schedule-assistant.component.ts` (1081 lines) — suggestion engine algorithm - Free/busy URL format: `{serverURL}/home/{email}?fmt=freebusy` **Implementation:** 1. New `components/calendar/free-busy-view.tsx` — attendee-row × time-slot grid 2. Color-coded slots: free (white), busy (red), tentative (yellow), out-of-office (purple) 3. Integration into event-modal when adding participants — show availability inline 4. Also queries `resources_bookings` table (see P2.8) to show resource availability in the same grid 5. New `lib/calendar-freebusy.ts` — fetch and parse free/busy data (user calendars + resource bookings) **Effort:** 16h (was 14h, added resource booking integration) ### P2.8 — Resources/Equipment Booking (VNCdirectory-backed PostgreSQL) **Docx:** "Missing: resources/equipment" **Architecture decision:** Resources managed in **separate PostgreSQL table**, mapped to **VNCdirectory** for centralized cross-application management (rooms, cars, equipment, etc.). Independent from mail client — this is a platform-level resource system. **Reuse from Angular:** - `calendar-equipment-dialog.component.ts` (373 lines) — equipment browser UI patterns - `calendar-equipment-autocomplete.component.ts` (266 lines) — autocomplete UX - GAL query: `zimbraCalResType === "Equipment"` → adapt to VNCdirectory resource type filter **Backend (New):** 1. **PostgreSQL migration** — `resources` table: - `id` UUID PK - `tenant_id` UUID (VNCdirectory tenant scope) - `name` text - `type` enum (room, vehicle, equipment, other) - `location` text (building/floor/room) - `capacity` integer nullable - `description` text - `contact_email` text (responsible person) - `is_active` boolean - `metadata` jsonb (extensible: photo URL, amenities, access hours, etc.) - `created_at`, `updated_at` timestamps 2. **`resources` table → VNCdirectory sync** — VNCdirectory is the canonical source. Options: - **Pull model:** VNCdirectory writes to this table via API/webhook - **Push model:** This service syncs changes back to VNCdirectory - **Read-through:** Query VNCdirectory directly for resource listings; cache in PostgreSQL for availability booking - Decision needed: which direction is authoritative? (Assume VNCdirectory → this table for Phase 1) 3. **New API routes:** `app/api/resources/` - `GET /api/resources` — list/search resources (type filter, location filter, tenant scoped) - `GET /api/resources/[id]` — single resource detail - `GET /api/resources/[id]/availability?start=&end=` — free/busy for a resource - `POST /api/resources/[id]/book` — create booking for a resource - `DELETE /api/resources/[id]/book/[bookingId]` — cancel booking 4. **New `lib/resources/` service layer:** - `lib/resources/client.ts` — fetch/query resources from VNCdirectory or local DB - `lib/resources/availability.ts` — check resource availability for time range - `lib/resources/booking.ts` — book/cancel resource - `lib/resources/sync.ts` — sync with VNCdirectory (if push-model needed) 5. **Conflict checking:** - When booking, check `resources_bookings` table for overlapping time ranges - Return conflicts + alternative slots **Frontend:** 1. Extend `components/calendar/event-modal.tsx` — "Resources" tab with: - Resource type filter (room, vehicle, equipment) - Searchable autocomplete (name, location, capacity) - Availability indicator (free/busy for event time range) 2. New `components/calendar/resource-picker.tsx` — reusable resource selection component 3. Booked resources appear in event detail, participant list, and email invitation **Effort:** 20h (was 10h, doubled for PostgreSQL + VNCdirectory integration) ### P2.9 — VNCtalk Video Meeting Integration **Docx:** "Missing: integration with VNCtalk -> create Videomeeting" **Reuse from Angular:** - `app.service.ts` → `createNewMeeting()` / `updateScheduledMeeting()` API calls - Payload: `POST /api/createnewmeeting { name, start, end, invitees, password, description, invid?, rev?, ms? }` - `createOrUpdateMeeting()` in `edit-appointment-dialog.component.ts` — builds payload from appointment **Implementation:** 1. New `lib/vnctalk/client.ts` — VNCtalk API client (create/update meeting) 2. Extend `components/calendar/event-modal.tsx` — "Create VNCtalk Meeting" toggle/button 3. Store meeting JID on CalendarEvent for updates 4. Add meeting link to event detail popover and email invitation body **Effort:** 8h ### P2.10 — Collabora Online Editing **Docx:** "Add feature to collaborate with Collabora" **Reuse from Angular:** - `owncloud.service.ts` → `getDocumentUrl(fileId, useCollabora)` with RichDocuments API - `POST ocs/v2.php/apps/richdocuments/api/v1/document?format=json` - Config: `collaboraBaseUrl` from admin config **Implementation:** 1. New `lib/collabora/client.ts` — fetch editing URL from Collabora server 2. New `components/files/collabora-editor.tsx` — iframe-based editor embedding 3. Extend `components/files/file-browser.tsx` — "Edit with Collabora" action for office files 4. Admin config: `collaboraBaseUrl` in policy/config **Effort:** 10h ### P2.11 — Calendar Enhancements Batch **Docx:** Multiple calendar improvements **2.11a — Clickable links in emails** - Already partially done (TipTap Link extension). Verify link rendering in calendar event descriptions. - Effort: 2h **2.11b — Contact details of participants** - Add popover on participant names in event-modal showing contact card. Reuse `components/contacts/contact-detail.tsx` data. - Effort: 4h **2.11c — Reply / Reply to All in meetings** - Extend event-modal with "Reply" and "Reply to All" buttons that open composer pre-filled with participant emails. - Effort: 3h **2.11d — Timezone support** - Add timezone picker to event-modal. Use `date-fns-tz` (already a dependency). Display times in event timezone with user timezone conversion. - Effort: 6h **2.11e — Map links** - Extract address from event location, generate Google Maps / OpenStreetMap link. - Effort: 2h ### P2.12 — Action Wheel (Custom Radial Menu) **Docx:** "Recreate Action Wheel or better functionality" **Board decision:** Build custom radial menu. **Implementation:** 1. New `components/ui/radial-menu.tsx` — SVG-based radial menu with configurable items 2. Supports: mail actions (reply, forward, delete, archive, mark read, move, tag), contact actions, file actions 3. Trigger: long-press on mobile, right-click on desktop, or dedicated button 4. Animations: CSS rotate + scale transitions 5. Keyboard accessible **Effort:** 10h ### P2.13 — IDP Integration — VNCdirectory Admin Configuration Panel **Docx:** "IDP integration will be towards VNCdirectory (openldap, simplesamlphp, 2fa etc.)" **Architecture decision:** Add admin UI for configuring VNCdirectory connection settings. Reuse auth patterns from VNCmail-analysis `api/auth-proxy/`. **Reuse from Angular (api/auth-proxy/):** - `config/config.js.example` — full auth configuration schema (SAML, LDAP, VNCdirectory, 2FA, hybrid auth) - `config/passport.js` — SAML strategy + JWT custom strategy setup - `routes/index.js` — login/logout/SAML callback/LDAP search/2FA/TOTP routes - `utils/common.js` — JWT verification, Zimbra preauth token creation - Auth dependencies: `@node-saml/passport-saml`, `passport`, `jsonwebtoken`, `ldapjs` **Current VNCmail+ auth stack vs Angular auth stack:** | Feature | Angular (Zimbra) | VNCmail+ (Stalwart) | |---------|-----------------|---------------------| | Primary auth | SAML 2.0 via Passport | OAuth/OIDC via Stalwart | | Identity source | Zimbra LDAP + VNCdirectory | Stalwart internal + OIDC | | 2FA | VNCdirectory TOTP/DUO | TOTP via Stalwart admin API | | Directory integration | Redmine API (`contactsApiUrl`) | ❌ None | | LDAP backend | `ldapjs` → Zimbra LDAP | ❌ None | | SSO/Federation | JWT deeplinks + SAML | Cookie-based + OAuth | **Implementation:** 1. **New `lib/vncdirectory/` service layer:** - `lib/vncdirectory/client.ts` — API client for VNCdirectory REST endpoints (port auth patterns from `routes/index.js`) - `lib/vncdirectory/config.ts` — VNCdirectory connection settings (URL, API key, LDAP bind, SAML IDP metadata) - `lib/vncdirectory/auth.ts` — SAML 2.0 SP implementation using `@node-saml/passport-saml` via Next.js API routes - `lib/vncdirectory/ldap.ts` — LDAP client using `ldapjs` for user/group/GAL queries - `lib/vncdirectory/2fa.ts` — TOTP enrollment + verification via VNCdirectory 2. **New API routes:** `app/api/vncdirectory/` - `GET /api/vncdirectory/status` — connection health check - `POST /api/vncdirectory/saml/login` — initiate SAML login flow - `POST /api/vncdirectory/saml/callback` — SAML assertion consumer - `POST /api/vncdirectory/saml/logout` — SAML single logout - `GET /api/vncdirectory/users` — search users (LDAP + VNCdirectory) - `POST /api/vncdirectory/2fa/enroll` — generate TOTP secret - `POST /api/vncdirectory/2fa/verify` — verify TOTP code - `GET /api/vncdirectory/2fa/status` — check 2FA enrollment status - `GET /api/vncdirectory/tags` — directory contact tags - `POST /api/vncdirectory/tags` — create/update directory tags 3. **New admin configuration page:** - `app/(main)/admin/vncdirectory/page.tsx` — VNCdirectory settings panel - Sections: - **Connection:** VNCdirectory URL, API key, LDAP URI, bind credentials - **SAML/IDP:** Identity Provider URL (SimpleSAMLphp), SP certificate, issuer - **Authentication:** Toggle SAML login, toggle 2FA enforcement, OIDC settings - **Directory sync:** LDAP type (OpenLDAP/MS-AD), search base, attribute mapping - **Federated apps:** Configure SSO URLs for VNCtalk, VNCtask, VNCcontacts - Add to admin navigation sidebar 4. **New `stores/vncdirectory-store.ts`** — client-side config state 5. **Extend existing auth:** - Add SAML login as alternative to existing Basic/OAuth flows - Add VNCdirectory as identity source alongside Stalwart - Wire 2FA through VNCdirectory (currently uses Stalwart admin API) **Effort:** 24h ### P2.14 — Share Files by Email as Attachment **Docx:** "share by email as attachment" **Implementation:** 1. Extend `components/files/file-browser.tsx` — "Send as Email Attachment" action 2. Opens composer with selected files attached (reuse existing attachment upload in composer) 3. Effort: 4h **P2 Total:** ~127h --- ## Phase 3 — Security Hardening (Autonomous) ### P3.1 — Feature Gate Server-Side Enforcement **Issue:** Feature gates are UI-only. Disabled features remain accessible via direct API calls. **Fix:** Add policy checks to API routes. For each feature-gated route, add `isFeatureEnabled()` check returning 403. - Routes: `app/api/smime/*`, `app/api/calendar-agenda/*`, `app/api/offline/*`, `app/api/plugins/*` (plugin disabled) - Effort: 4h ### P3.2 — Unified Auth Error Interceptor **Issue:** 401/403 errors silently swallowed in data fetches. **Fix:** Create `lib/auth-error-handler.ts` — global fetch wrapper that detects 401 and triggers re-auth flow. Wire into JMAP client `authenticatedFetch`. - Effort: 6h ### P3.3 — Store-Level State Isolation on Account Switch **Issue:** Manual `clearAllStores()` misses new fields and stores. **Fix:** Define per-store `snapshot(): Partial` and `clear(): Partial` contract. Auto-discover registered stores via a registry. - Effort: 8h ### P3.4 — Push Event Bus Extraction **Issue:** Email store is the push dispatch hub for 5+ stores. **Fix:** Extract `lib/push-event-bus.ts` — stores subscribe to JMAP type names. Email store stops importing calendar/filter/task stores. - Effort: 8h **P3 Total:** ~26h --- ## Phase 4 — Polish & Remaining HIGH Items ### P4.1 — Offline Write Queue **Issue:** No offline write capability. Cannot compose/send while offline. **Fix:** Add `replica_pending_ops` table. Stage mutations offline, replay on connectivity return. Start with email send only, then extend. - Effort: 16h ### P4.2 — Identity Spoofing Protection **Issue:** From override accepts arbitrary addresses. **Fix:** Client-side validation — restrict `fromOverrideEmail` to domains matching user's identities. - Effort: 2h ### P4.3 — WebSocket Push for Electron **Issue:** Browser WebSocket push permanently disabled. **Fix:** Implement main-process WebSocket bridge in Electron via IPC. Renderer sends token, main process connects WS with auth header. - Effort: 8h ### P4.4 — Remaining MEDIUM audit findings - Calendar: recurrence cap warning, prefix scheme unification, read-only calendar filter, bulk delete batching - Contacts: cross-account move race, autocomplete indexing, import dedup - Files: folder tree cache reuse, upload parallelism - Search: `toWildcardQuery` quote handling, `searchEmails` AbortController - Effort: ~20h **P4 Total:** ~46h --- ## Summary | Phase | Hours | Description | |-------|-------|-------------| | P1 | 32h | Critical + HIGH bug fixes (18 items) | | P2 | 167h | Missing features from docx (14 features) | | P3 | 26h | Security hardening (4 items) | | P4 | 46h | Polish + remaining fixes (4 items) | | **Total** | **~271h** | | ### New Infrastructure Dependencies (P2) - **PostgreSQL database** — `resources` + `resources_bookings` tables for VNCdirectory-backed resource management - **VNCdirectory** — canonical source for resources + IDP identity provider (SAML 2.0, LDAP, 2FA/TOTP, directory tags) - **SimpleSAMLphp** — SAML 2.0 Identity Provider (`vncidp.dev.vnc.de`) for web SSO - **OpenLDAP** — LDAP directory for user/group queries and GAL (via `ldapjs`) - **Collabora server** — `collaboraBaseUrl` admin config for online document editing - **VNCtalk API** — `/api/createnewmeeting` endpoint for video meeting integration ### Autonomy Level - **100% autonomous** — No further board decisions needed. - **Sync direction (VNCdirectory ↔ PostgreSQL)**: Assumed VNCdirectory → PostgreSQL (pull) for Phase 1. Can be swapped if VNCdirectory expects push updates. - **Code reuse:** 12 areas from VNCmail-analysis Angular codebase (models, API patterns, business logic). Must be adapted from Angular DI/services to plain TS functions + React hooks. - **Repository access:** `brvncde-dotcom/vncmail-plus` (target), `brvncde-dotcom/VNCmail-analysis` (reuse). ### Deploy Flow Per policy: P1 → deploy to `dev` → QA → fix → promote to `main`. Then P2 → dev → QA → main. Repeat for P3, P4. ### Phase 1 — COMPLETED 2026-08-07 **Shipped as v1.7.9.** 17 of 18 fixes deployed to `main`. All 2527 tests pass (161 test files). One item deferred: P1.3 (C5 auth localStorage encryption) — requires custom Zustand persist adapter, planned for Phase 3. --- Do you want me to start Phase 1 immediately, or adjust the plan?