ultisuite-client/.cursor/plans/drive_suite_build_7921c055.plan.md
R3D347HR4Y 8a02c10ba3 Add environment configuration and update email view components
- Created a .cursorignore file to manage local environment files.
- Updated .env.example to reflect changes in the public app URL.
- Modified the gmail workspace configuration to include the drive-suite path.
- Enhanced email view components to support attachment handling and fallback for plain text bodies.
- Improved user experience by updating attachment display logic and integrating inline attachment support.
2026-06-04 00:12:43 +02:00

410 lines
19 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
name: Drive Suite Build
overview: "Build UltiDrive as a Google Drivelike 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 Drivelike 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, ~12 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 (45 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 |