--- name: Drive Suite Build overview: "Build UltiDrive as a Google Drive–like frontend in `drive-suite`, with all backend/infra in `ulti-backend` (ultid API, Nextcloud, OnlyOffice, Authentik, deploy). Mail stays frontend-only in `gmail-interface-clone`. Production routing: same domain at `/drive`." todos: - id: phase0-provisioning content: Fix drive EnsurePrincipal + extend drive API (quota, shares CRUD, restore, favorite, create blank file) status: completed - id: phase0-onlyoffice content: Deploy OnlyOffice Document Server, enable NC onlyoffice app, add /api/v1/office session+callback endpoints status: completed - id: phase1-scaffold content: Scaffold drive-suite Next.js app (auth, API client, basePath /drive, app shell, URL routing) status: completed - id: phase2-mvp content: Build Google Drive file browser MVP wired to real /api/v1/drive (upload, browse, share, trash, recent, starred) status: completed - id: phase3-editor content: Integrate OnlyOffice editor embed for docx/xlsx/pptx via /drive/edit/{id} status: completed - id: phase4-chrome content: "Google-like editor chrome: OnlyOffice theme first, then custom wrapper using drive-dump specs if needed" status: completed - id: phase5-suite content: "Wire suite integration: nginx /drive route, launcher hrefs, mail attachment picker, Authentik callbacks" status: completed isProject: false --- # UltiDrive (Drive Suite) — Build Plan ## Repository roles | Repo | Responsibility | |------|----------------| | **[`ulti-backend`](file:///Users/red/workdev/ulti-backend)** | **All backend infrastructure and server-side code:** custom Go API (`ultid`), Docker Compose, edge nginx, Authentik, Postgres/KeyDB/RustFS, headless Nextcloud, mail stack (IMAP/SMTP/sync), OnlyOffice Document Server, Jitsi, observability, deploy scripts, env templates, Authentik blueprints. This is the single ops + API hub for the suite. | | **[`gmail-interface-clone`](file:///Users/red/workdev/gmail-interface-clone)** | **Frontend only — Ultimail.** Next.js UI for mail (list, compose, settings, contacts). Talks to ulti-backend via `/api/v1` proxy; no server infrastructure lives here. | | **[`drive-suite`](file:///Users/red/workdev/drive-suite)** | **Frontend only — UltiDrive.** Next.js UI for the Google Drive–like file browser and editor shell. Same pattern as mail: API calls go to ulti-backend; deploy wiring (nginx route, compose service) is defined in ulti-backend. | Suite frontends are thin clients; every persistent service and integration runs from **ulti-backend**. --- ## Current state | Layer | Status | |-------|--------| | [`drive-suite/`](file:///Users/red/workdev/drive-suite) | Empty placeholder — greenfield | | [`ulti-backend`](file:///Users/red/workdev/ulti-backend) | Headless NC 30 deployed; `/api/v1/drive` proxy (list, upload, download, move, copy, rename, trash, recent, starred, shares) | | [`gmail-interface-clone`](file:///Users/red/workdev/gmail-interface-clone) | Auth stack, shadcn UI, app shell patterns; UltiDrive tile exists but has no `href` | | [`ultimail/apps/web/src/components/drive/`](file:///Users/red/workdev/ultimail/apps/web/src/components/drive) | Mock UI components (file-browser, share-dialog, upload-zone) — reference only | | [`drive-dump/`](file:///Users/red/workdev/drive-dump) | Google Docs chrome research (titlebar, toolbar CSS specs) — useful for editor skinning, not for file browser | | OnlyOffice | Not deployed; mentioned only in [`project-plan/ultidrive.md`](file:///Users/red/workdev/ulti-backend/project-plan/ultidrive.md) | **Critical backend bug to fix first:** drive handlers pass `claims.Sub` as NC user ID, but NC OIDC maps `preferred_username` (email). Contacts already use `EnsurePrincipal(email, sub, name)` — drive must match or provisioning fails. --- ## Target architecture ```mermaid flowchart TB subgraph frontends [Frontend repos] MailFE["gmail-interface-clone"] DriveFE["drive-suite"] end subgraph ultiBackend [ulti-backend deploy and API] Nginx["edge nginx"] Ultid["ultid Go API"] Auth["Authentik"] NC["Nextcloud headless"] OO["OnlyOffice"] MailSvc["mail IMAP SMTP sync"] S3["RustFS S3"] end MailFE -->|"/mail"| Nginx DriveFE -->|"/drive"| Nginx Nginx --> Ultid Nginx --> Auth Ultid --> DriveAPI["/api/v1/drive"] Ultid --> OfficeAPI["/api/v1/office"] Ultid --> MailAPI["/api/v1/mail"] Ultid --> WS["/ws"] DriveAPI --> NC OfficeAPI --> NC OfficeAPI --> OO NC --> OO NC --> S3 MailAPI --> MailSvc ``` **Principles:** - Browser never talks to Nextcloud or OnlyOffice directly — all via **ultid** in ulti-backend (auth, RBAC, stable API). - Nextcloud UI stays hidden (`/cloud/` admin-only); users only see UltiDrive. - Same Authentik OIDC session; production path `/drive` on the suite domain (shared cookies with `/mail`). --- ## Phase 0 — Backend hardening ([`ulti-backend`](file:///Users/red/workdev/ulti-backend) only) All work in this phase stays in ulti-backend (Go API + `deploy/`). No infrastructure or API logic belongs in drive-suite or gmail-interface-clone. Fix and extend the existing drive layer before building UI. ### 0.1 User provisioning fix In [`internal/api/drive/handlers.go`](file:///Users/red/workdev/ulti-backend/internal/api/drive/handlers.go) and `service.go`, replace raw `claims.Sub` with: ```go userID, err := nc.EnsurePrincipal(ctx, claims.Email, claims.Sub, claims.Name) ``` Mirror the contacts pattern in [`internal/nextcloud/users.go`](file:///Users/red/workdev/ulti-backend/internal/nextcloud/users.go). ### 0.2 API gaps (priority order) | Endpoint | Purpose | |----------|---------| | `GET /drive/quota` | Storage bar in UI | | `GET /drive/shares?path=` | List existing shares | | `PUT /drive/shares/{id}` | Update role / expiry / password | | `DELETE /drive/shares/{id}` | Revoke share | | `POST /drive/trash/restore` | Restore from trash | | `POST /drive/favorite` | Star/unstar | | `POST /drive/files/new` | Create empty doc/sheet/slide (NC template or OO blank) | | `GET /drive/search` | Full-text (v1: name filter; v2: Meilisearch) | Extend [`internal/nextcloud/drive.go`](file:///Users/red/workdev/ulti-backend/internal/nextcloud/drive.go) with OCS share list/update/delete and favorite WebDAV properties. ### 0.3 WebSocket events Add `drive.file_changed`, `drive.share_updated` to ultid WS hub so drive UI can invalidate TanStack Query cache (same pattern as mail events in [`gmail-interface-clone/lib/api/ws.ts`](file:///Users/red/workdev/gmail-interface-clone/lib/api/ws.ts)). ### 0.4 OnlyOffice + Nextcloud deployment New files under [`deploy/onlyoffice/`](file:///Users/red/workdev/ulti-backend/deploy): - `docker-compose.onlyoffice.yml` — Document Server container - Update [`deploy/nextcloud/init.sh`](file:///Users/red/workdev/ulti-backend/deploy/nextcloud/init.sh) — `$OCC app:enable onlyoffice` - Update [`deploy/compose-up.sh`](file:///Users/red/workdev/ulti-backend/deploy/compose-up.sh) — conditional include when `ONLYOFFICE_ENABLED=true` - Update [`deploy/nginx/default.conf.template`](file:///Users/red/workdev/ulti-backend/deploy/nginx/default.conf.template): - `/drive/` → drive-suite container - `/office/` → OnlyOffice (internal, JWT-protected; not exposed to users directly) Env vars to add to `.env.example`: ``` ONLYOFFICE_ENABLED=true ONLYOFFICE_URL=http://onlyoffice:80 ONLYOFFICE_JWT_SECRET=... ONLYOFFICE_PUBLIC_URL=https://{DOMAIN}/office ``` ### 0.5 Office API (new package `internal/api/office/`) | Endpoint | Role | |----------|------| | `POST /office/session` | Given `{ path, mode: view\|edit }`, return OnlyOffice editor config + signed JWT | | `GET /office/callback` | OnlyOffice save callback → NC WebDAV PUT | | `POST /office/create` | Create blank docx/xlsx/pptx in user folder | Flow: drive UI opens `/drive/edit/{fileId}` → backend builds OnlyOffice config pointing at NC file via internal URL → OnlyOffice loads document → saves via callback. --- ## Phase 1 — Scaffold [`drive-suite`](file:///Users/red/workdev/drive-suite) (Google Drive shell) Frontend-only repo. Copy **UI/auth/client patterns** from [`gmail-interface-clone`](file:///Users/red/workdev/gmail-interface-clone) (the mail frontend), not from ultimail (different router). Match stack: **Next.js 16, React 19, Tailwind 4, shadcn new-york, Zustand 5, TanStack Query 5, pnpm**. ### 1.1 Repo bootstrap ``` drive-suite/ ├── app/ │ ├── layout.tsx # QueryProvider + AuthProvider │ ├── page.tsx # redirect → /drive │ ├── api/auth/ # copy OIDC routes from mail │ ├── auth/complete/ │ ├── login/ │ └── drive/ │ ├── layout.tsx # DriveAppShell │ ├── [[...segments]]/page.tsx │ ├── edit/[fileId]/page.tsx # OnlyOffice embed (Phase 3) │ └── settings/[[...section]]/ ├── components/drive/ # file browser, sidebar, header, share, upload ├── lib/ │ ├── auth/ # session, pkce, jwt-claims │ ├── api/client.ts # Bearer + 401 refresh │ ├── api/hooks/use-drive-queries.ts │ ├── drive-url.ts # URL = source of truth │ └── stores/ # drive-ui-store, drive-settings-store ├── next.config.mjs # basePath: '/drive' in prod; /api/v1 rewrite └── Dockerfile # same multi-stage pattern as mail ``` **`basePath: '/drive'`** in production so assets and routes work behind nginx path routing. ### 1.2 Auth Copy auth stack from mail; change: - Default `returnTo` → `/drive` - Persist key → `ultidrive-auth` - Keep `ulti_*` cookie names (same origin) ### 1.3 App shell (Google Drive UX) Three-zone layout mirroring [`mail-app-shell.tsx`](file:///Users/red/workdev/gmail-interface-clone/app/mail/mail-app-shell.tsx): ``` ┌──────────────────────────────────────────────────────────┐ │ Header: search, view toggle, account, suite launcher │ ├──────────┬───────────────────────────────────────────────┤ │ Sidebar │ Main: breadcrumb + file grid/list │ │ My Drive │ │ │ Recent │ (optional detail panel for file preview) │ │ Starred │ │ │ Trash │ │ │ Storage │ │ └──────────┴───────────────────────────────────────────────┘ ``` Port/adapt from ultimail mock components: - [`file-browser.tsx`](file:///Users/red/workdev/ultimail/apps/web/src/components/drive/file-browser.tsx) — grid/list, sort, multi-select - [`breadcrumb-nav.tsx`](file:///Users/red/workdev/ultimail/apps/web/src/components/drive/breadcrumb-nav.tsx) - [`upload-zone.tsx`](file:///Users/red/workdev/ultimail/apps/web/src/components/drive/upload-zone.tsx) — drag-drop + chunked upload via `X-Upload-*` headers - [`share-dialog.tsx`](file:///Users/red/workdev/ultimail/apps/web/src/components/drive/share-dialog.tsx) - [`quota-bar.tsx`](file:///Users/red/workdev/ultimail/apps/web/src/components/drive/quota-bar.tsx) Add `--drive-*` CSS tokens in `globals.css` parallel to mail's `--mail-*`. ### 1.4 URL routing [`lib/drive-url.ts`](file:///Users/red/workdev/drive-suite/lib/drive-url.ts) + `use-drive-route.ts`: | URL segment | View | |-------------|------| | `/drive` | My Drive root | | `/drive/folders/documents/page/2` | Folder + pagination | | `/drive/recent` | Recent files | | `/drive/starred` | Favorites | | `/drive/trash` | Trash | | `/drive/search?q=report` | Search results | | `/drive/file/{id}` | Preview panel | | `/drive/edit/{id}` | Editor (Phase 3) | Wire real API from day one — no mock data. --- ## Phase 2 — Core Drive features (MVP) Ship a usable Google Drive clone before editors. | Feature | Backend | Frontend | |---------|---------|----------| | Browse folders | `GET /drive/files/*` | Grid/list + breadcrumb | | Upload (simple + chunked) | `POST /drive/files/*` | Progress, retry | | Create folder | `POST /drive/folders/*` | New folder dialog | | Rename / move / copy | existing endpoints | Context menu + drag-drop move | | Delete / trash / restore | DELETE + trash endpoints | Trash view | | Starred / recent | existing endpoints | Sidebar views | | Share (link + users) | shares CRUD | Share dialog (owner/editor/viewer) | | Download | `GET /drive/download/*` | Context menu | | Preview | download + mime routing | Images, PDF inline; others download | | Storage quota | `GET /drive/quota` | Sidebar bar | | Multi-select bulk ops | batch endpoints or sequential | Toolbar actions | **New file affordance (Google-style):** - Dropdown: Blank document / spreadsheet / presentation / folder / upload - Creates file via `POST /office/create` or NC template, then opens editor --- ## Phase 3 — OnlyOffice editor integration ### 3.1 Embed pattern [`app/drive/edit/[fileId]/page.tsx`](file:///Users/red/workdev/drive-suite/app/drive/edit/[fileId]/page.tsx): 1. Fetch editor config from `POST /api/v1/office/session` 2. Load `@onlyoffice/document-editor-react` (or iframe with DocsAPI) 3. Full-viewport editor; minimal chrome initially ### 3.2 File type routing | MIME / extension | Editor | |------------------|--------| | `.docx`, `.odt` | Document | | `.xlsx`, `.ods` | Spreadsheet | | `.pptx`, `.odp` | Presentation | | Other | Preview or download only | ### 3.3 Save / co-editing OnlyOffice handles real-time co-editing natively when connected to NC via the OnlyOffice app. Backend callback ensures saves land in NC WebDAV. No custom CRDT needed for v1. --- ## Phase 4 — Google-like editor chrome (theme → wrapper → fork) Decision gate after Phase 3 ships with stock OnlyOffice UI. ### Step A — OnlyOffice theme (try first, ~1–2 weeks) - Custom JSON skin: toolbar colors, fonts (Roboto/Google Sans), icon set - CSS overrides via OnlyOffice `customization` config - Hide OnlyOffice branding; match light/dark to suite theme ### Step B — Custom chrome wrapper (if theme insufficient) Leverage [`drive-dump`](file:///Users/red/workdev/drive-dump) research: - [`docs/CHROME-SPEC.md`](file:///Users/red/workdev/drive-dump/docs/CHROME-SPEC.md) — titlebar, menubar, toolbar DOM/CSS specs - [`web/components/chrome/`](file:///Users/red/workdev/drive-dump/web/components/chrome) — React chrome components - Build suite-owned titlebar (doc title, share, star, account) **above** OnlyOffice iframe - Pass `customization.layout` to hide OnlyOffice's own header/toolbar - Separate chrome per app type: Docs, Sheets, Slides (toolbar sets differ) ### Step C — Fork (last resort) Only if theme + wrapper cannot reach Google parity on toolbars/menus. Fork OnlyOffice Document Server frontend (AGPL) — high maintenance cost. **Defer until A+B evaluated.** Per [`drive-dump/docs/EDITOR-CORE-DECISION.md`](file:///Users/red/workdev/drive-dump/docs/EDITOR-CORE-DECISION.md): do **not** rebuild the editing engine — OnlyOffice remains the core; only chrome is customized. --- ## Phase 5 — Suite integration ### 5.1 Cross-app navigation - [`header-account-actions.tsx`](file:///Users/red/workdev/gmail-interface-clone/components/gmail/header-account-actions.tsx): add `href: "/drive"` to UltiDrive tile - Reciprocal launcher in drive-suite header back to `/mail`, Agenda, etc. - Shared suite favicon/branding via `pnpm run brand:authentik` pattern ### 5.2 Mail ↔ Drive - **Mail UI** (gmail-interface-clone): compose attachment picker "Insert from UltiDrive"; "Save attachment to Drive" on received mail - **API** (ulti-backend): mail attachment upload → `POST /drive/files/...` in ultid ### 5.3 Deploy wiring (ulti-backend) All routing and compose changes live in **ulti-backend** [`deploy/`](file:///Users/red/workdev/ulti-backend/deploy). The drive-suite repo supplies the Docker image / build context only. Update [`deploy/nginx/default.conf.template`](file:///Users/red/workdev/ulti-backend/deploy/nginx/default.conf.template): ```nginx location /drive/ { proxy_pass http://drive-suite:3000/; # WebSocket + standard headers } ``` Add a `drive-suite` service to ulti-backend compose (port 3000 internal; dev: build from `../drive-suite` or host :3001). Mail frontend is deployed the same way today — gmail-interface-clone is not part of ulti-backend source tree, but its container is orchestrated from ulti-backend deploy. Update Authentik blueprints / OIDC redirect URIs in ulti-backend for `/drive/api/auth/callback`. --- ## Phase 6 — Advanced (post-MVP) | Feature | Approach | |---------|----------| | Full-text search | Meilisearch indexer on NC files + `/drive/search` | | Version history | NC versions API wrapper | | Shared drives / team folders | NC `groupfolders` app + API | | Public link sharing page | `/drive/shared/{token}` read-only view | | Desktop mount | rclone sidecar in Tauri (per ultidrive.md) | | Offline uploads | IDB queue pattern from mail offline-queue | | Comments on files | OnlyOffice comments (built-in) or NC comments API | --- ## Suggested delivery order ```mermaid gantt title UltiDrive delivery phases dateFormat YYYY-MM-DD section ulti backend API and provisioning :a1, 2026-06-02, 14d OnlyOffice deploy :a2, after a1, 7d section drive suite Scaffold :a3, 2026-06-02, 7d Drive MVP :a4, after a3, 21d Editor embed :a5, after a2, 14d Google chrome :a6, after a5, 21d section integration Suite wiring :a7, after a4, 7d ``` Parallel tracks: **a1→a2** (ulti-backend) runs alongside **a3→a4** (drive-suite); **a5** starts after OnlyOffice (**a2**); **a7** (suite wiring) after Drive MVP (**a4**). **First shippable milestone (4–5 weeks):** Phase 0 + 1 + 2 + 5 nginx/auth — full Drive file browser on real backend. **Second milestone (+3 weeks):** Phase 3 — open and edit docs/sheets/slides in OnlyOffice. **Third milestone (+3 weeks):** Phase 4 — Google-like editor chrome. --- ## Key files to create/modify | Repo | Role | Files | |------|------|-------| | **ulti-backend** | Backend + all infra | `internal/api/drive/*`, `internal/api/office/*`, `deploy/onlyoffice/*`, `deploy/nginx/*`, compose, Authentik blueprints | | **drive-suite** | Drive frontend only | Entire Next.js app; copy auth + API client patterns from gmail-interface-clone | | **gmail-interface-clone** | Mail frontend only | `header-account-actions.tsx`, compose drive picker (UI links to `/drive`) | | **Reference only** | — | `ultimail/.../drive/*`, `drive-dump/docs/CHROME-SPEC.md` | --- ## Risks and mitigations | Risk | Mitigation | |------|------------| | NC user ID mismatch | Fix EnsurePrincipal in Phase 0 before any UI work | | OnlyOffice JWT / NC connector misconfig | Test with single doc open before building chrome | | Chunk upload edge cases | Reuse existing `X-Upload-*` protocol; add integration tests | | Google UI parity scope creep | Theme first; wrapper second; fork only as last resort | | Path-based routing (`/drive`) asset breaks | `basePath` in next.config from day one |