# Zync OS Advanced (Plan C) Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: executed via run-plan harness (codex seats). Steps use checkbox (`- [ ]`) syntax for tracking. Audience: AI coding agents first.

**Goal:** Advanced OS features (virtual desktops, workspaces, focus, lock, folders, taskbar/snap/shortcuts, activity center, health, tenant switcher, Module Manager) + white-label shell + portal/staff/admin shells on an extracted `packages/os-shell`.

**Architecture:** Spec is source of truth: `docs/specs/2026-07-11-zync-os-advanced-design.md` (SHIPPED, 9 codex review rounds). Prerequisite: Plan B LANDED (this plan's schema/guards/purge-message/BottomSheet seams come from B). Every task points to its spec section; the spec pins contracts, this plan pins files/waves/acceptance.

**Tech Stack:** React + zustand + TanStack Query, Hono, Drizzle/Postgres (Neon), Workers rate-limit binding, Playwright, zod.

**Design doctrine:** `.claude/skills/zc-ui-ux-designer/` — REQUIRED READING for every UI task; §14 amendments land BEFORE dependent UI waves (gate).

---

## Wave Plan

| Wave | Tasks | Files touched (primary) | Safe to parallelize? |
|------|-------|------------------------|----------------------|
| 1 | T1 extraction, T2 schema-v2, T3 skill-amend, T4 audit, T5 tokens | packages/os-shell (git mv) / packages/modules shell-layout-schema.ts + api route / skill files / docs artifact / packages/ui tokens css | ✅ T1 moves `apps/zync-app/src/os/**`→package (import rewrite); T2 owns the schema file T1 does NOT move (already in packages/modules per Plan B) |
| 2 | T6 desktops, T7 ws-backend, T8 tmpl-backend, T9 auth-backend, T10 wl-backend | os-shell store+overview / db migration+routes shell-workspaces / db migration+routes shell-templates / security-settings.ts+reauth.ts+me / white-label.ts+portal bootstrap+proxy+manifest-route+migration | ✅ four backend tasks in separate route/migration files (migration numbers assigned by T4 artifact to avoid collision); T6 client-only |
| 3 | T11 workspaces-ui, T12 focus, T13 lock, T14 activity+health, T15 chrome, T16 folders, T17 module-manager | os-shell features/* — one feature dir per task | ✅ per-feature dirs disjoint |
| 4 | T18 wl-client, T19 switcher, T20 templates-ui | branding utils+boot / switcher feature / roles settings + boot fallback | ✅ disjoint |
| 5 | T21 portal-shell, T22 control-center | apps/zync-app/src/portal/** / apps/zync-admin/** | ✅ different apps |
| 6 | T23 final gate | verification only | single |

## Decision gates

Run-level `g1` binds `base_branch` = `master` — VERIFY at pre-flight that Plan B's merge is an ancestor of origin/master (C consumes B's seams: shared schema, guards, purge ack, BottomSheet). Abort if B not landed. No task-level gates (migrations standard flow; portal-shell-default and classic-portal-chrome deletion are spec-settled).

---

### Task 1: `packages/os-shell` extraction (Wave-1 enabler)

**Wave:** 1 · **Blocks:** T6, T11–T22 · **Blocked by:** —
**Files:** Create `packages/os-shell/**` (git mv from `apps/zync-app/src/os/**` engine files + import rewrite + package.json `file:` dep per commit `7cd6f` depth-independent law); Modify zync-app to host-adapter (`ShellHost` construction, manifest→descriptor mapping).
**Contract:** spec §0 (read in full — `ShellAppDescriptor`/`ShellCapabilities`/`HostAppBindings`/`ShellHost`/`ShellPersistenceClient`/`ShellFeatureSet` verbatim; wire-key law `moduleId`; appId≡ModuleId identity unit test; composite persistence client for zync-app; per-host feature entrypoints).
**Behavior:** ZERO behavior change — mechanical extraction.
**Acceptance:** FULL A+B suites green UNCHANGED (the parity test); bundle parity ±5%. Falsifier fallback recorded at gate if HMR/bundle breaks.
- [ ] Extract; adapter; run full A+B suites; commit

### Task 2: Layout schema v2 (single owner)

**Wave:** 1 · **Blocks:** T6, T11, T15, T16, T20 · **Blocked by:** —
**Files:** Modify `packages/modules/src/shell-layout-schema.ts`, `apps/zync-api/src/routes/shell-layout.ts` (envelope `v: 1|2`).
**Contract:** spec §1 (read in full): `EnvelopeV2` (envelope owns `v`; data has none), `DesktopDataV2`/`MobileDataV2` supersets incl. `onboarding`, strict zod, `migrateLayoutEnvelope(raw) → EnvelopeV2 | null` explicit per-source rules, client-side migration on read.
**Acceptance:** migrator unit matrix PASS (unversioned/garbage, v1 desktop incl. onboarding round-trip, v1 mobile); route accepts both versions.
- [ ] Failing matrix; implement; run; commit

### Task 3: Skill amendments (§14) + Task 5: tokens

**Wave:** 1 · **Blocks:** T6, T11–T22 (gate) · **Blocked by:** —
**T3 Files:** Modify `.claude/skills/zc-ui-ux-designer/{SKILL.md,references/desktop.md,references/mobile.md}` per spec §14 list. **T5 Files:** Modify `packages/ui/src/tokens/index.css` (`--z-lock`, `--z-window-pinned`, wallpaper dim recipe).
**Acceptance:** amendment sections present (grep per §14 list); tokens exported.
- [ ] Amend; verify; commit (two tasks, disjoint files)

### Task 4: Audit task

**Wave:** 1 · **Blocks:** T7–T10 (migration numbers), T14 · **Blocked by:** —
**Files:** Create `docs/plans/audit/zync-os-advanced-audit.md`.
**Contract:** spec §Current-state anchors + §6/§7 sources: enumerate exact job/integration endpoints + query shapes; record security-settings exact field names + settings UI section; assign next free migration numbers to T7/T8/T10; verify `RATE_LIMITER_AUTH` binding name.
**Acceptance:** artifact complete; no unresolved unknowns for waves 2–3.
- [ ] Audit; write artifact; commit

### Task 6: Virtual desktops + overview

**Wave:** 2 · **Blocks:** T11, T12, T19 · **Blocked by:** T1, T2, T3
**Files:** Modify os-shell store (desktop-scoped selectors), Create `packages/os-shell/features/virtual-desktops/**` (overview, switch, move).
**Contract:** spec §2 (read in full): max 6; capacity-aware delete/move (block > 12, toast); overview live-active-only; URL replaceState on switch + cross-desktop popstate; global mounted cap 12 LRU via `canDiscard` + cap-exhaustion reject.
**Acceptance:** desktop-scoped store matrix + landed WM matrix re-run PASS; overview/switch probes PASS.
- [ ] Failing matrices; implement; run; commit

### Task 7: shell_workspaces backend

**Wave:** 2 · **Blocks:** T11 · **Blocked by:** T4
**Files:** Create migration `00XX_shell_workspaces.sql` (number from T4), `apps/zync-api/src/routes/shell-workspaces.ts`, `packages/db/src/queries/shell-workspaces.ts` + route tests.
**Contract:** spec §3 backend (read in full): exact DDL; routes GET/POST/PATCH/DELETE; FIELD-level authz (`shared` = `requirePermission('settings:write')` only, 403); caps under `pg_advisory_xact_lock`; version 409; every mutation audited; tenant-guard dual-layer.
**Acceptance:** authz matrix route tests PASS incl. cross-tenant 404, non-admin shared 403, concurrent cap race test, 409.
- [ ] Failing route tests; implement; run; verify migration against prod schema flow; commit

### Task 8: shell_templates backend

**Wave:** 2 · **Blocks:** T20 · **Blocked by:** T4
**Files:** Create migration `00XX_shell_templates.sql`, `apps/zync-api/src/routes/shell-templates.ts` + tests.
**Contract:** spec §11 backend: exact DDL incl. composite FK `(tenant_id, role_id) → roles(tenant_id, id)` + CHECK device_class + `updated_at NOT NULL default now()`; GET member-read-own-role / PUT admin-only; zod §1 schema; ≤64KB.
**Acceptance:** route tests PASS incl. cross-tenant role_id rejected by FK.
- [ ] Failing tests; implement; run; commit

### Task 9: Lock/auth backend (security-settings + reauth limiter + has_password)

**Wave:** 2 · **Blocks:** T13, T19 · **Blocked by:** T4
**Files:** Modify `apps/zync-api/src/routes/security-settings.ts` (+ `auto_lock_minutes` column migration if the settings table needs it — T4 records), `apps/zync-api/src/routes/reauth.ts` (rate limiter), me/session payload (`has_password`).
**Contract:** spec §5 backend bullets (read in full): cross-field refine on EFFECTIVE merged pair (`auto_lock_minutes < idle-logout`, null valid); limiter `RATE_LIMITER_AUTH` key `reauth:<userId>`, deny → 429 + `Retry-After: 60`, fail-closed; `has_password` derived from `password_hash IS NOT NULL`.
**Acceptance:** route tests PASS (validation grid, N+1 → 429 + header, has_password both states).
- [ ] Failing tests; implement; run; commit

### Task 10: White-label backend (columns + PATCH + wallpaper + portal bootstrap + manifest route)

**Wave:** 2 · **Blocks:** T18, T21 · **Blocked by:** T4
**Files:** Create migration `00XX_white_label_shell.sql` (`wallpaper_url`, `hide_powered_by`); Modify `apps/zync-api/src/routes/settings/white-label.ts` (GET + PATCH per spec — PATCH excludes `wallpaper_url`), wallpaper upload/DELETE routes, public tenant-asset proxy (allowlist regex `wallpaper-[a-f0-9]{8}\.(png|jpg|webp)`), portal bootstrap (`white_label` object, plan-gated), Create manifest worker route `GET /manifest.webmanifest` (Host vs `custom_domain` WHERE `ssl_status='active'`).
**Contract:** spec §9 (read in full — versioned-key upload state machine verbatim: write → persist → compensate → cleanup; DELETE nulls first).
**Acceptance:** route tests PASS (PATCH field set, plan gate, upload magic-byte/5MB rejects, replacement flips URL + old key 404, proxy regex, manifest host-hit/miss/pending-ssl, portal bootstrap gated null).
- [ ] Failing tests; implement; run; commit

### Task 11: Workspaces UI · Task 12: Focus mode · Task 13: Lock screen · Task 14: Activity + health · Task 15: Chrome (taskbar/snap/top/shortcuts) · Task 16: Folders · Task 17: Module Manager

**Wave:** 3 · **Blocked by:** T6 (+T7 for T11, +T9 for T13, +T4 for T14, +T2 for T15/T16) · **Blocks:** T23
**Files:** one `packages/os-shell/features/<feature>/**` dir per task (disjoint); T17 adds `module_manager` osApp binding in zync-app.
**Contracts (each reads its spec §):** T11→§3 client (apply via canDiscard, permission-filter, copy-on-apply, caps/409 surfaces); T12→§4 (principal-keyed sessionStorage record, priority kinds, exact restore); T13→§5 client (overlay `--z-lock`, display:none privacy, typed reauth results incl. oauth_only/rate_limited, "Sign in again" path); T14→§6+§7 (query-option factories + ONE `useQueries`, partitioned ring + purge hooks, health rollup); T15→§8 chrome bullets (position/auto-hide RTL-logical, thirds ≥1440 + group resize, always-on-top `--z-window-pinned`, SHORTCUT_DEFAULTS + conflicts + overlay); T16→§8 folders (FolderRecord, popover/overlay, dissolve-when-empty, keyboard semantics); T17→§8 Module Manager (toggleable+plan-gated grid, system excluded, disable via canDiscard, dependency warnings).
**Acceptance per task:** its unit matrix + behavioral probes + baselines named in spec §Verification; a11y contract rows for its surfaces.
- [ ] Per task: failing tests → implement → probes/baselines → commit

### Task 18: White-label client application

**Wave:** 4 · **Blocked by:** T10 · **Blocks:** T21, T23
**Files:** Create OKLCH accent utility + contrast guard, wallpaper application (desktop/lock/mobile home), brand-name splash/header/title; settings UI section.
**Contract:** spec §9 application bullet (contrast 4.5:1 fallback + admin health warn).
**Acceptance:** contrast-guard unit matrix; branding e2e (accent/wallpaper/name applied; fallback path).
- [ ] Implement; run; commit

### Task 19: Tenant switcher

**Wave:** 4 · **Blocked by:** T6, T9 · **Blocks:** T23
**Files:** Create `packages/os-shell/features/tenant-switcher/**` + zync-app host wiring.
**Contract:** spec §10 state machine (read in full): 2FA branches inline (reuse login 2FA component), point of no return at 200, awaited SW purge ack (2s timeout + telemetry), clear TanStack + activity ring + focus record, forward-complete after 200.
**Acceptance:** e2e state-machine walk incl. mocked `requires_2fa`, pre-200 abort, post-200 forward; single-membership hides switcher.
- [ ] Failing e2e; implement; run; commit

### Task 20: Staff templates UI + boot fallback

**Wave:** 4 · **Blocked by:** T8 · **Blocks:** T23
**Files:** Modify roles settings (Desktop templates section, edit-as-role shell mode), boot layout resolution (lazy fallback chain).
**Contract:** spec §11 (read in full): LAZY FALLBACK single model — no row written on fallback render; personal row only from user's own first save; built-in staff constant for roles lacking `settings:write`.
**Acceptance:** acceptance-12 e2e (fallback render no-write, first save wins, template edit reaches non-personalized member).
- [ ] Failing e2e; implement; run; commit

### Task 21: Customer portal shell

**Wave:** 5 · **Blocked by:** T1, T10, T18 · **Blocks:** T23
**Files:** Modify `apps/zync-app/src/portal/**` (PortalShell host, os-bindings/, `principal_key` consumption); delete classic portal chrome AFTER acceptance-11 e2e passes.
**Contract:** spec §12 (read in full): stacked shell all devices, capabilities all-false, features NONE imported, search-only command center, resolved basePath from `:tenantSlug`, localStorage-only persistence, white-label via bootstrap `white_label`.
**Acceptance:** all 7 portal apps reachable; email deep links; portal-session revocation e2e still green; bundle assertion (no feature code).
- [ ] Implement; run; delete classic chrome; commit

### Task 22: Zync Control Center (admin)

**Wave:** 5 · **Blocked by:** T1 · **Blocks:** T23
**Files:** Modify `apps/zync-admin/**` (os-shell adoption, descriptor list = existing pages, token overlay).
**Contract:** spec §13: capabilities all-false, features none, `lockScreen:false`, Control-Center token overlay (tokens only), SUPER_ADMIN guard untouched, zero tenant-app leakage.
**Acceptance:** admin e2e on SUPER_ADMIN fixture; bundle assertion; distinct-identity baseline.
- [ ] Implement; run; commit

### Task 23: Final gate

**Wave:** 6 · **Blocked by:** ALL
**Contract:** spec §Acceptance criteria 1–15 walked with evidence; vision judge (distinct-but-same-family criterion for portal/admin); fault-injection list from §Verification; full A+B+C suites green.
- [ ] Walk criteria; record; commit

---

## Self-review notes

- Spec coverage: §0→T1, §1→T2, §2→T6, §3→T7/T11, §4→T12, §5→T9/T13, §6/§7→T14, §8→T15/T16/T17, §9→T10/T18, §10→T19, §11→T8/T20, §12→T21, §13→T22, §14→T3/T5, backend list items 1–10 all mapped. No gaps.
- Same-wave overlap: wave-2 backend tasks in separate route+migration files, numbers pre-assigned by T4; wave-3 tasks each own a feature dir. T1 moves os/** while T2 edits packages/modules file — disjoint.
- Seam names (`ShellFeatureSet`, `principalKey`, `migrateLayoutEnvelope`, `settings:write`, `reauth:<userId>`) identical to spec.
- Contracts point to spec sections; no bodies in plan.
