fix: Phase 1 critical+high fixes (17/18 items)

CRITICAL fixes:
- C1: Error swallowing - throw TransportError on network failure in getEmails/searchEmails
- C2: Recurrence expansion ID delimiter changed from ':' to '::occurrence::'
- C3: Cross-account calendar event UID dedup after multi-account aggregation
- C4: Admin session token revocation via JTI blacklist on logout
- C6: FTS5 schema-drop - add warning log for automatic reindex trigger
- C7: Settings lock - gate updateSetting() with isSettingLocked() check
- C8: Offline push pause - add offline event handler that closes push transports

HIGH fixes:
- H1: Push handler - add ContactCard and FileNode branches
- H2: WS fallback - await state snapshot before reconcileAfterWebSocketFallback
- H3: Auth rate limiting - add checkUserAuthRateLimit to session and token routes
- H4: OAuth logs - strip access_token from error log context
- H7: Template XSS - apply DOMPurify to HTML template body on import
- H8: Secure cookie - derive from x-forwarded-proto, not NODE_ENV
- H9: bcrypt fix - remove bcrypt prefixes from isHashed() so scrypt-only
- H13: calendarTasksEnabled - apply admin gate at runtime in calendar page
- H14: Task mutations - add try/catch error handling to update/delete/toggle
- H18: autoSelectReplyIdentity default changed from false to true

Deferred: P1.3 (C5 auth localStorage encryption) - requires custom Zustand persist adapter.
This commit is contained in:
Bernd Rodler
2026-08-07 12:40:32 +02:00
parent 4653de6d30
commit 47b9ab4398
21 changed files with 1450 additions and 40 deletions
@@ -0,0 +1,514 @@
# 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<S>` and `clear(): Partial<S>` 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.
### First Sprint Scope
**Phase 1 only** — ship all 8 CRITICAL + 10 HIGH fixes (~32h). This brings health from 7.2 to ~8.5/10 and addresses the most impactful user-facing bugs before adding new features.
---
Do you want me to start Phase 1 immediately, or adjust the plan?