# Architecture ## Overview The collab-editor is a collaborative article editor built for Hugging Face Spaces. It runs as a single Docker container serving both the backend (Express + Hocuspocus) and the frontend (React + TipTap). ``` ┌─────────────────────────────────────────────────────────┐ │ Docker Container (port 8080) │ │ │ │ ┌──────────────────┐ ┌────────────────────────────┐ │ │ │ Express Server │ │ Hocuspocus (Y.js collab) │ │ │ │ │ │ │ │ │ │ /api/* │ │ /collab (WebSocket) │ │ │ │ /published/* │ │ │ │ │ │ /editor │ │ │ │ │ │ / (published) │ │ │ │ │ └──────────────────┘ └────────────────────────────┘ │ │ │ │ ┌──────────────────┐ ┌────────────────────────────┐ │ │ │ Publisher │ │ Frontend (static) │ │ │ │ (HTML renderer) │ │ /editor -> SPA │ │ │ │ (PDF generator) │ │ React + TipTap │ │ │ └──────────────────┘ └────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘ ``` ## Key directories | Path | Description | |------|-------------| | `backend/src/server.ts` | Entry point: imports `createApp()`, starts listener, signal handlers | | `backend/src/create-app.ts` | Express app factory: routes, Hocuspocus, WebSocket, middleware | | `backend/src/publisher/` | HTML rendering, PDF generation, bibliography formatting | | `backend/src/publisher/html-renderer.ts` | Converts TipTap JSON to static HTML page | | `backend/src/publisher/extensions.ts` | Server-side TipTap extensions (mirrors frontend) | | `backend/src/shared/component-defs.ts` | Shared component definitions (single source of truth) | | `backend/src/utils.ts` | Shared utilities: `docPath()`, `sanitizeName()`, injectable `DATA_DIR` | | `backend/src/auth.ts` | OAuth flow, token extraction, user resolution | | `backend/src/hf-storage.ts` | HF dataset sync (push/pull documents and assets) | | `frontend/src/editor/` | TipTap editor, toolbars, components | | `frontend/src/editor/components/registry.ts` | Component registry (imports from shared defs) | | `frontend/src/styles/` | All CSS files | ## Styling architecture ### CSS layers The project uses five CSS layers, loaded in this order by `main.tsx`: 1. **Template foundation** (`_variables.css`, `_reset.css`, `_base.css`, `_layout.css`, `_print.css`, `components/*`) - Defines the article's visual identity (typography, grid, components) - Uses CSS custom properties (`--text-color`, `--surface-bg`, `--primary-color`, etc.) - Layout tokens centralize grid math (`--layout-toc-width`, `--layout-content-width`, `--layout-gap`, breakpoints) - Shared between the editor preview and the published output 2. **Editor chrome** (`_ui.css`) - Styles the editor UI: top-bar, sidebars, dialogs, chat panel, embed studio - Uses `--ed-*` custom properties for the dark editor theme - Only loaded in the editor, never in published output 3. **Design tokens** (`tokens.css`) - Light/dark theming tokens for both editor and article - Text, background, border, accent, code highlighting, danger, shadows - Supports `data-theme` attribute and `prefers-color-scheme` media query - Shared between editor and published output (injected by publisher) 4. **Shared article styles** (`article.css`, `toc.css`) - Article content styles shared between editor preview and published output - `article.css` includes wrapper components (Note, Stack, Quote, Sidenote...), with editor-specific variants scoped by `.editor-app` 5. **Editor-only styles** (`styles/editor/*.css`, 5 files) - `_layout.css`: grid overrides (3-col symmetric, aligned with template), TOC drawer, responsive breakpoints (1100px collapse, 768px mobile) - `_chrome.css`: ProseMirror editing visuals (placeholder, selection, cursors, comment marks, math editing) - `_block-tools.css`: block handles (drag + add) and slash menu - `_panels.css`: image upload card, footnote tooltip, citation panel - `_hero-editable.css`: transparent click-to-edit inputs for FrontmatterHero 6. **Publisher CSS** (`_publisher.css`) - Styles specific to the published static HTML page - Theme toggle animations, wide/fullWidth breakout, sidenote float, lightbox, PDF link - Only injected by the HTML renderer (backend), never loaded in the editor ### No CSS-in-JS The project does not use MUI, Emotion, or any CSS-in-JS library. All styling is done via: - CSS custom properties for theming - Vanilla CSS files with BEM-like class naming - `Floating UI` for tooltip positioning (lightweight, no CSS-in-JS) ### Color theming - The article area uses `data-theme="light"` / `data-theme="dark"` with CSS variable overrides - The editor chrome is always dark, using `--ed-*` tokens - The primary accent color is controlled via `--primary-color` (synced via Yjs settings) ## HF Spaces constraints ### Iframe embedding When deployed as a HF Space, the app runs inside an iframe. This affects: - **Viewport width**: the iframe is ~968px wide, not the full browser width - **No `target="_top"` navigation**: links open within the iframe unless using `target="_blank"` - **OAuth flow**: the OAuth callback URL must match the Space URL - **CSP restrictions**: sandboxed iframes may restrict certain APIs ### Two-page architecture | URL | What it serves | |-----|----------------| | `/` | Published article (static HTML) or login prompt | | `/editor` | The SPA editor (requires authentication) | This means: - The published article is a completely standalone HTML file - It does NOT load React or any JS framework - The editor is a separate React SPA at `/editor` ### CSS cascade Because the published HTML is self-contained (all CSS inlined in `