tfrere's picture
tfrere HF Staff
docs: refresh README, architecture, spec and add agent teams proposal
32a4aca

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 <style>), there are no CSS conflicts with the HF Spaces iframe CSS. The editor uses Vite's CSS pipeline.

Shared component registry

Component definitions (name, kind, fields, defaults) are defined once in backend/src/shared/component-defs.ts. This file is the single source of truth.

  • Backend (extensions.ts): imports SHARED_COMPONENT_DEFS to generate TipTap server extensions for generateHTML()
  • Frontend (registry.ts): imports SHARED_COMPONENT_DEFS via Vite alias #shared and decorates each entry with UI metadata (icon, label, description, placeholders)

Adding a new component:

  1. Add the entry to shared/component-defs.ts
  2. Add UI metadata to frontend/src/editor/components/registry.ts in UI_META
  3. Add CSS for the published view to frontend/src/styles/_publisher.css

Publisher pipeline

Y.Doc -> TiptapTransformer -> JSON -> generateHTML() -> postProcess() -> full HTML page
                                                                            |
                                                                            v
                                                                    PDF (Playwright)
                                                                            |
                                                                            v
                                                                    Upload to HF dataset

Post-processing (linkedom)

The postProcess() function uses linkedom for DOM manipulation instead of regex:

  • Accordion <div> -> <details>/<summary>
  • Citation <span> -> <a> links with bibliography anchors
  • Bibliography placeholder -> formatted HTML with entry IDs
  • Mermaid <div> -> <pre class="mermaid">
  • HtmlEmbed <div> -> <iframe>
  • Footnotes -> superscript links + appended section

Preview endpoint

GET /api/preview/:docName renders the HTML without saving or uploading. Useful for testing the publisher pipeline.

Auth and security

  • AI chat routes (/api/chat, /api/embed-chat) are auth-guarded when OAuth is enabled
  • The requireEditor middleware checks cookie token and verifies write access
  • Published articles are served without auth (public)

Testing

Tests use Vitest + Supertest. Run with npm test from backend/.

Test file What it covers
tests/publisher.test.ts Y.Doc extraction, HTML generation, post-processing, idempotency
tests/html-renderer-snapshot.test.ts Snapshot tests for each postProcess transformation
tests/security.test.ts XSS prevention in published HTML
tests/css-resolution.test.ts @custom-media resolution
tests/utils.test.ts Path sanitization utilities
tests/auth.test.ts Token extraction, OAuth configuration
tests/hf-storage.test.ts HF dataset storage configuration
tests/persistence.test.ts Local file persistence, debounced save
tests/api-routes.test.ts API route integration tests (publish, auth status)