Commit Graph

2 Commits

Author SHA1 Message Date
97f94f247b draw.io reference plugin: fullscreen editing, inline SVG rendering
All checks were successful
CD / Build and push images (push) Successful in 3m54s
CD / Deploy to Test (push) Successful in 10s
CI / Lint, typecheck, test (push) Successful in 4m9s
CI / Build container images (push) Has been skipped
CD / Smoke tests against Test (push) Successful in 1m13s
CD / Promote to Int (push) Successful in 13s
CI / Auth e2e pack (push) Successful in 5m36s
CI / Import/export fidelity gate (push) Successful in 47s
A new block plugin bundling the OFFICIAL draw.io editor — nothing ever
loads from diagrams.net; the sandbox CSP pins every request to the
plugin's own version-pinned asset path (zero-external-network verified
live via a request-capture run).

Plugin (packages/plugins/drawio):
- block data { xml, svg }: xml is the draw.io source (document of
  record), svg the rendered snapshot as raw markup — render mode,
  office/PDF exports (the existing fallback renderer already inlines
  data.svg) and the public view all show the diagram without running
  diagram code
- edit mode: snapshot + "edit in fullscreen" (an empty block opens the
  editor immediately); the bundled editor runs in a child iframe of the
  plugin's own assets and speaks draw.io's JSON embed protocol —
  Save & Exit exports xmlsvg, persists { xml, svg } via blockData, and
  drops back to the inline size
- build.mjs fetches the pinned release (v30.3.6) into a gitignored
  vendor/ cache (fonts-build pattern; skipped in CI — plugin.js still
  bundles, the installable ZIP needs a dev machine) and packs a trimmed
  webapp subset: no dev sources, no embed.diagrams.net integrations
  bundle, no standalone viewers, no MathJax/templates/PWA — 27 MiB ZIP,
  85 MiB unpacked, de+en editor languages

Host/SDK extensions (generic, not drawio-specific):
- new ui.enterFullscreen()/exitFullscreen(): the surface's frame becomes
  a viewport-covering overlay — same sandboxed iframe, only geometry
  changes; destroy removes the frame, so a vanished plugin can never
  leave the app covered
- sandbox CSP: connect-src/frame-src now allow the plugin's OWN asset
  path (was 'none') — bundled apps lazy-load their resources and run in
  a child frame, but the api and external hosts stay unreachable; HTML
  assets are served with the same CSP so a packaged page cannot widen
  the rules, and child frames inherit the sandbox attribute
- plugin size limits raised (ZIP 5→64 MiB, unpacked 20→256 MiB) for
  bundled-app plugins; content types for xml/txt/ico assets

Verified end to end against a local stack (9/9): install via dropzone
(85 MiB validation), block insert, fullscreen entry, bundled editor
boots inside the double sandbox (German UI), shape drawn, Save & Exit
persists, snapshot renders inline, survives reload, zero off-origin
requests throughout.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
2026-07-12 14:15:30 +02:00
621aa47244 Add plugin storage, install API, and directory watcher (#71)
Some checks failed
CI / Auth e2e pack (push) Waiting to run
CI / Import/export fidelity gate (push) Waiting to run
CI / Build container images (push) Waiting to run
CD / Build and push images (push) Failing after 1m33s
CD / Deploy to Test (push) Has been skipped
CD / Smoke tests against Test (push) Has been skipped
CD / Promote to Int (push) Has been skipped
CI / Lint, typecheck, test (push) Has been cancelled
Backend for installing plugin ZIPs (ADR 0008, plugin-architecture.md
§Lifecycle, security.md §Plugins). Consumes the #70 SDK for validation.

- Schema: `plugins` (id, name, version, apiVersion, kind, mode, manifest
  jsonb, removedAt soft-delete) + `pond_plugins` (per-pond activation) +
  `PluginInstanceMode` enum; migration 20260710130000_plugins.
- `PluginPackageService`: pure, stateless ZIP → validated package via
  fflate — structure check, manifest validation (SDK), apiVersion gate,
  kind/bundle/styles rules, CSS sanitation (no @import / external url() /
  expression()), zip-slip and unpacked-size guards. Each failure carries a
  stable PluginErrorCode; manifest issues travel as ApiError details.
- `PluginStorageService`: on-disk layout `<PLUGINS_DIR>/<id>/<version>/`;
  atomic writeVersion (staging dir + rename, no 404 window mid-update),
  removeVersion/removePlugin, traversal-safe asset resolution, dropzone +
  quarantine dirs.
- `PluginsService`: install/update (update only to a strictly higher
  version, preserving the admin's instance mode; files land before the
  metadata pointer flips) / uninstall (refused while required; soft-delete
  + files removed + pond activations dropped) / list / get.
- `POST/GET/DELETE /admin/plugins` (SiteAdminGuard, multer memory upload),
  error→HTTP-status mapping. Public version-pinned static serving at
  `GET /plugins/:id/:version/*rest` with immutable cache + nosniff, only for
  the installed current version.
- `PluginWatcherService`: watches `<PLUGINS_DIR>/_dropzone/`, runs the same
  validation, installs valid drops and quarantines invalid ones with the
  error logged; inert under NODE_ENV=test (tests drive processDropped).
- SDK: `compareVersions`/`isHigherVersion`. shared: `PluginView`,
  `PluginInstanceMode`, `PLUGIN_ERROR_CODES`, `PLUGINS_DIR` env, plugin
  error i18n (de+en). Compose: `plugins` volume + `PLUGINS_DIR`.
- Tests: package unit test (valid + each invalid class) and an e2e DB test
  (GUI install + immutable serving, non-admin 403, invalid-manifest details,
  dropzone install + quarantine, atomic higher-only update, required-guarded
  uninstall that removes files and tombstones metadata).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
2026-07-10 16:55:26 +02:00