feat(workspaces): images in documents, flashcards, and quizzes

UR
2mo ago
+1931-271

Study material is full of diagrams, but ThinkEx documents silently dropped pasted images, cards and quizzes forbade them, and the AI had no way to see one. A student copying a labeled cell diagram into their notes just lost it.

What was asked: let users put images in documents (and later cards/quizzes), and let the AI read, find, and place them — descriptions by default, actual pixels only on demand.

What this does:

  • Documents: paste, drag-drop, or the toolbar button inserts an image. Each one becomes a hidden workspace file item owned by the document — out of the sidebar, exempt from the upload meter (capped at 100 per owner, embeddable owner types only, so the exemption can't be scripted into free storage), described by the existing Gemini extraction, and purged with its owner unless another item still embeds it (Outline-style reference check at delete). Pasted web content gets its external images downloaded server-side behind the existing SSRF policy — never hotlinked.
  • Flashcards & quizzes: the entry allowlist now permits the shared image node, the AI authoring surfaces know the <img data-item-id> contract, and the study viewers render bounded thumbnails with click-to-expand for free via the shared schema.
  • AI: reads return each embedded image's stored description in a new images field beside the content — kept out of the HTML so replace_text edit anchors stay byte-exact. A new view_image tool attaches actual pixels (workspace path/ref or public URL) for one model step; web_fetch returns to pages only.
  • Exports: PDFs inline preview bytes as data URLs via the DOM the renderer already builds; ZIP markdown links rewrite to relative archive paths.

Why this shape: every image is an ordinary workspace file whatever door it entered through (upload, paste, web import), so storage, previews, descriptions, deletion, and the AI tool are each built once. The ownership model follows Outline's production-tested attachment design: reference-sharing on copy, GC only at owner delete, orphans err toward keeping bytes.

Reviewed before filing: an adversarial pass (4 majors found and fixed — edit-anchor corruption, Office-paste screenshots, a metering bypass, a sidecar leak) and an over-engineering pass (verdict: appropriately sized; trims applied). Known accepted tradeoffs are commented in code (ponytail: entries): the ~8s copy-then-delete race, one-time upload-token invalidation at deploy, and deferred ref-based image addressing.

Testing: pnpm dev for editor flows; pnpm dev:full for real Gemini descriptions, view_image, URL imports, and PDF export. 442 unit tests pass; typecheck/lint clean.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Summary by CodeRabbit

  • New Features
    • Added workspace image support across documents, flashcards, quizzes, and rich-text content.
    • Upload images from the toolbar, clipboard, or drag-and-drop, including imported web images.
    • View, expand, and preview workspace images in documents and AI chat.
    • Added AI image viewing for public URLs and workspace images.
    • Images are included in PDF and archive exports.
  • Bug Fixes
    • Hidden image attachments no longer appear as standalone workspace items.
    • Added safeguards for unsupported files, oversized images, and failed image loads.