forma-diagrams 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/Dockerfile +16 -0
  2. package/LICENSE +21 -0
  3. package/README.md +113 -0
  4. package/THIRD_PARTY_NOTICES.md +20 -0
  5. package/bin/forma +3 -0
  6. package/deploy/.env.example +11 -0
  7. package/deploy/Caddyfile +4 -0
  8. package/deploy/compose.yml +38 -0
  9. package/dist/assets/IBMPlexSans-Regular-Bl2SjS7V.ttf +0 -0
  10. package/dist/assets/IBMPlexSans-SemiBold-B9auKknr.ttf +0 -0
  11. package/dist/assets/elk.bundled-Y7ymokgr.js +24 -0
  12. package/dist/assets/index-_kg9cdik.css +1 -0
  13. package/dist/assets/index-al0xDdTG.js +452 -0
  14. package/dist/fonts/IBMPlexSans-Regular.ttf +0 -0
  15. package/dist/fonts/IBMPlexSans-SemiBold.ttf +0 -0
  16. package/dist/fonts/OFL.txt +93 -0
  17. package/dist/index.html +14 -0
  18. package/dist/licenses/FORMA_LICENSE.txt +21 -0
  19. package/dist/licenses/THIRD_PARTY_LICENSES.txt +11601 -0
  20. package/docs/api.md +41 -0
  21. package/docs/decisions/001-foundation.md +57 -0
  22. package/docs/decisions/002-composition-and-human-edits.md +47 -0
  23. package/docs/decisions/003-general-composition.md +30 -0
  24. package/docs/decisions/004-library-and-distribution.md +33 -0
  25. package/docs/decisions/005-organizational-hosting.md +33 -0
  26. package/docs/decisions/006-hosted-agent-access.md +25 -0
  27. package/docs/format.md +132 -0
  28. package/docs/gallery.md +73 -0
  29. package/docs/images/architecture.png +0 -0
  30. package/docs/images/editor.png +0 -0
  31. package/docs/images/gallery/architecture.png +0 -0
  32. package/docs/images/gallery/decision.png +0 -0
  33. package/docs/images/gallery/development-signal.png +0 -0
  34. package/docs/images/gallery/development.png +0 -0
  35. package/docs/images/gallery/editor-roundtrip.png +0 -0
  36. package/docs/images/gallery/entities.png +0 -0
  37. package/docs/images/gallery/mindmap.png +0 -0
  38. package/docs/images/gallery/organization.png +0 -0
  39. package/docs/images/gallery/timeline.png +0 -0
  40. package/docs/images/release.png +0 -0
  41. package/docs/install.md +50 -0
  42. package/docs/plans/mvp.md +24 -0
  43. package/docs/self-hosting.md +167 -0
  44. package/docs/verification-v2.md +41 -0
  45. package/docs/verification-v3.md +16 -0
  46. package/docs/verification-v4.1.md +35 -0
  47. package/docs/verification-v4.2.md +20 -0
  48. package/docs/verification-v4.3.md +5 -0
  49. package/docs/verification-v4.md +31 -0
  50. package/docs/verification-v5.1.md +7 -0
  51. package/docs/verification-v5.2.md +7 -0
  52. package/docs/verification-v5.md +7 -0
  53. package/docs/verification.md +91 -0
  54. package/examples/design-systems/atelier.json +53 -0
  55. package/examples/design-systems/signal.json +53 -0
  56. package/examples/gallery/architecture.forma.json +178 -0
  57. package/examples/gallery/decision.forma.json +182 -0
  58. package/examples/gallery/development-signal.forma.json +299 -0
  59. package/examples/gallery/development.forma.json +299 -0
  60. package/examples/gallery/entities.forma.json +123 -0
  61. package/examples/gallery/mindmap.forma.json +158 -0
  62. package/examples/gallery/organization.forma.json +151 -0
  63. package/examples/gallery/timeline.forma.json +138 -0
  64. package/examples/platform.forma.json +116 -0
  65. package/examples/release.forma.json +58 -0
  66. package/examples/roundtrip/agent-continued.forma.json +310 -0
  67. package/examples/roundtrip/agent-patch.json +7 -0
  68. package/examples/roundtrip/human-edited.forma.json +309 -0
  69. package/package.json +81 -0
  70. package/packages/cli/bin.mjs +3 -0
  71. package/packages/cli/src/agent-access.ts +232 -0
  72. package/packages/cli/src/blob-store.ts +241 -0
  73. package/packages/cli/src/file-library.ts +179 -0
  74. package/packages/cli/src/google.ts +51 -0
  75. package/packages/cli/src/hosted.ts +498 -0
  76. package/packages/cli/src/http.ts +67 -0
  77. package/packages/cli/src/index.ts +397 -0
  78. package/packages/cli/src/mcp.ts +114 -0
  79. package/packages/cli/src/remote.ts +228 -0
  80. package/packages/cli/src/server.ts +69 -0
  81. package/packages/cli/src/signed-cookie.ts +42 -0
  82. package/packages/core/src/align.ts +237 -0
  83. package/packages/core/src/document.ts +408 -0
  84. package/packages/core/src/font-metrics.json +1 -0
  85. package/packages/core/src/geometry.ts +463 -0
  86. package/packages/core/src/index.ts +12 -0
  87. package/packages/core/src/inspect.ts +230 -0
  88. package/packages/core/src/layout.ts +450 -0
  89. package/packages/core/src/render.ts +182 -0
  90. package/packages/core/src/resolve-style.ts +29 -0
  91. package/packages/core/src/scene.ts +40 -0
  92. package/packages/core/src/styles.ts +122 -0
  93. package/packages/core/src/text.ts +81 -0
  94. package/packages/core/src/theme.ts +33 -0
  95. package/public/fonts/IBMPlexSans-Regular.ttf +0 -0
  96. package/public/fonts/IBMPlexSans-SemiBold.ttf +0 -0
  97. package/public/fonts/OFL.txt +93 -0
  98. package/schema/design-system.v1.schema.json +734 -0
  99. package/schema/forma.v1.schema.json +273 -0
  100. package/schema/forma.v2.schema.json +1756 -0
  101. package/skills/forma/SKILL.md +20 -0
  102. package/skills/forma/references/composition.md +11 -0
  103. package/skills/forma/references/hosted.md +28 -0
  104. package/skills/forma-architecture/SKILL.md +14 -0
  105. package/skills/forma-entities/SKILL.md +14 -0
  106. package/skills/forma-flow/SKILL.md +14 -0
  107. package/skills/forma-mindmap/SKILL.md +14 -0
  108. package/skills/forma-organization/SKILL.md +14 -0
  109. package/skills/forma-timeline/SKILL.md +14 -0
package/docs/api.md ADDED
@@ -0,0 +1,41 @@
1
+ # Programmatic core
2
+
3
+ The source API is TypeScript/ESM. This repository is not yet published as a registry package. Use a TypeScript-aware runtime such as `tsx` or a bundler.
4
+
5
+ ```ts
6
+ import {
7
+ parseDocument,
8
+ patchDocument,
9
+ serializeDocument,
10
+ layoutDiagram,
11
+ inspectScene,
12
+ renderSvg,
13
+ } from './packages/core/src/index.ts';
14
+
15
+ const document = parseDocument(JSON.parse(nativeFileContents));
16
+ const updated = patchDocument(document, {
17
+ nodes: [{ id: 'api', label: 'Public API' }],
18
+ });
19
+ const scene = await layoutDiagram(updated);
20
+ const report = inspectScene(scene);
21
+ const svg = renderSvg(scene);
22
+ const nativeJSON = serializeDocument(updated);
23
+ ```
24
+
25
+ `parseDocument(unknown)` validates and applies defaults, returning a `Diagram`. `patchDocument(diagram, unknown)` returns a validated new document without mutating the input. `serializeDocument(diagram)` returns stable, indented JSON with a trailing newline.
26
+
27
+ `layoutDiagram(diagram)` asynchronously returns a `Scene`. Layout is implemented using ELK and a composition layer. `inspectScene(scene)` returns machine-readable `issues` with `code`, `severity`, `message`, and affected `ids`, plus a `summary` with error and warning counts. Pinned nodes that sit within 8px of a shared row or column produce `near-alignment` warnings with a `fix` edge and target. `alignNodes`, `distributeNodes`, and `mergeAlignmentPins` produce position pins for `patchDocument`. Inspect after human positioning as well as semantic changes.
28
+
29
+ `renderSvg(scene, { fontDataUri?, boldFontDataUri? })` returns SVG text. Supply regular and semibold font data URIs for a portable, embedded-font SVG. The CLI embeds the bundled static IBM Plex Sans Regular and SemiBold fonts and registers both with resvg to generate PNG at 2× scale. PNG output above 32 million pixels is rejected before rasterization; use SVG or reduce diagram spread and pinned positions for larger diagrams.
30
+
31
+ ## Adapter boundary
32
+
33
+ Adapters should read native documents, invoke the core, then write documents or derived exports. They should not store editor-specific state as the canonical format. The browser editor may keep transient viewport/selection state separately; only meaningful document edits belong in version control.
34
+
35
+ Future exporters should accept `Scene` plus its embedded semantic document. Geometry is available without scraping SVG. This is the intended path for editable draw.io, VSDX, and PowerPoint shapes and connectors. Version those exporters independently of the native schema. External tool protocols, including MCP, can wrap this API or the CLI without affecting the core.
36
+
37
+ ## General composition and design systems
38
+
39
+ `migrateDocument(unknown)` validates and losslessly upgrades a v1 document to v2. `designSystemSchema.parse(unknown)` validates standalone visual identities; `atelier` and `signal` are bundled examples. Apply one with `patchDocument(doc, {designSystem: system})`. The style cascade is resolved into each scene element's `style`, so adapters can consume editable geometry and resolved appearance directly.
40
+
41
+ Set `layout.mode: 'grid'` and node `placement: {column,row}` for intentional relative composition. Otherwise ELK infers layered positions. Both paths respect human pins and use the same typography, group bounds, routing, label placement, inspector, and renderer. See [the native format](format.md) for the style vocabulary and context-specific property behavior.
@@ -0,0 +1,57 @@
1
+ # ADR 001: a document engine with three adapters
2
+
3
+ Status: accepted · 2026-09-20
4
+
5
+ Forma proves architecture diagrams and process flows, two themes, nested groups,
6
+ stable semantic IDs, persistent human overrides, SVG/PNG exports, a file-oriented
7
+ CLI, and a static graphical editor. The project is MIT licensed.
8
+
9
+ ## Research and alternatives
10
+
11
+ - [ELK / elkjs](https://github.com/kieler/elkjs), EPL-2.0: mature layered layout,
12
+ compound graphs, labels and orthogonal routing. Selected as an unmodified dependency.
13
+ [Algorithm reference](https://eclipse.dev/elk/reference/algorithms/org-eclipse-elk-layered.html).
14
+ - [Dagre](https://github.com/dagrejs/dagre), MIT: simpler directed layout, but compound
15
+ grouping and routing would require more of our own infrastructure. Not selected.
16
+ - [React Flow](https://github.com/xyflow/xyflow), MIT: selected for pan/zoom,
17
+ selection, keyboard interaction, node dragging and connection handles. Its state
18
+ is disposable view state, never the document format.
19
+ - [tldraw](https://tldraw.dev/community/license): production SDK licensing does not
20
+ satisfy the no-proprietary-dependencies requirement. Not selected.
21
+ - [resvg-js](https://github.com/thx/resvg-js), MPL-2.0: native local SVG rasterization
22
+ for CLI PNG exports; browser exports use SVG + Canvas. No rendering service.
23
+
24
+ Research read on 2026-09-20. Lockfile records actual dependency versions.
25
+
26
+ ## Decision
27
+
28
+ A pure TypeScript core validates and patches documents, measures and wraps text,
29
+ composes ELK geometry, routes pinned-node edges, inspects layout, and renders SVG.
30
+ Node and browser adapters handle I/O. Exporters consume a resolved scene of native
31
+ rectangles, text and polylines, enabling later editable VSDX/PPTX/draw.io exporters.
32
+ We do not rasterize the scene inside the native artifact.
33
+
34
+ The composition layer owns deliberate type scale, an 8px rhythm, comfortable
35
+ container padding, semantic accents, title/footer framing, and connector labels.
36
+ Layout alone is not the product. SVG primitives are shared by editor and exports.
37
+
38
+ ## Human / agent merge contract
39
+
40
+ Version 1 JSON contains semantic nodes, edges, groups and layout intent separately
41
+ from presentation overrides. Patches upsert by ID and preserve unrelated overrides.
42
+ Moves pin absolute positions. Auto layout retains pins; resetting them is explicit.
43
+ Deleting a node removes incident relationships and its own override. Renaming an
44
+ ID is a delete/add and cannot automatically preserve identity. Groups are derived
45
+ containers; group names/accents and node membership are editable, bounds are not.
46
+
47
+ Resolved scenes carry a document fingerprint and can be emitted separately for
48
+ inspection. The MVP does not load those files or implement a geometry cache;
49
+ rendering always computes a fresh scene from the native document. Derived layout
50
+ is not required in Git. Serialization is stable with no wall-clock timestamps.
51
+
52
+ ## Scope boundaries
53
+
54
+ No accounts, required DB, hosted AI, collaboration server, MCP, freehand drawing,
55
+ or arbitrary vector illustration. No claim of global crossing-free routing.
56
+ Inspector diagnostics are geometric heuristics, with explicit thresholds. Pinned
57
+ conflicts are reported, never silently 'fixed' by moving the human's node.
@@ -0,0 +1,47 @@
1
+ # ADR 002: composition, routing and typography
2
+
3
+ Status: accepted · 2026-09-20
4
+
5
+ ELK provides hierarchical directed layout and orthogonal routes. Forma adds:
6
+
7
+ 1. Text-aware node dimensions using bundled font advance widths, wrapping and an
8
+ 8px sizing rhythm. Both regular and semibold static fonts are embedded in exports.
9
+ 2. Consistent group padding, type scale, muted secondary text, restrained semantic
10
+ accents, emphasis and a title/footer frame.
11
+ 3. Compression of empty bands between top-level containers. Compound layouts can
12
+ otherwise reserve disproportionate whitespace; only wholly empty bands shrink.
13
+ 4. Label candidates scored against components, headings and existing labels.
14
+ 5. Human position reconciliation after automatic composition. Pins remain absolute;
15
+ group bounds expand to contain the moved descendants.
16
+ 6. Selective rerouting of edges touching moved nodes or crossing their new positions.
17
+ Unaffected automatic routes and node geometry remain stable for position-only edits.
18
+
19
+ Pinned routes use an orthogonal visibility grid with bend penalties, respecting
20
+ component obstacles. Impossible overlapping pins are preserved and diagnosed,
21
+ not silently repositioned. The MVP does not guarantee crossing-free routing or
22
+ optimal placement under arbitrary constraints. Label placement and spacing checks
23
+ are deterministic geometric heuristics. Review rendered diagrams as well as reports.
24
+
25
+ ## Corrections discovered during implementation
26
+
27
+ ELK can store an edge in the root array while identifying a nested coordinate
28
+ container. Resolve the reported container offset before using its sections.
29
+
30
+ ELK ignores plain compound width hints in some layouts. Use minimum-size
31
+ constraints to ensure group headings fit, including after human moves.
32
+
33
+ The initial variable font did not produce matching weights in resvg and browsers.
34
+ Use upstream static IBM Plex Sans Regular and SemiBold files in both adapters.
35
+
36
+ Keyboard moves and pointer drags both emit completed position changes through
37
+ React Flow. Persist those changes through the document patch API; never leave
38
+ keyboard edits solely in editor view state.
39
+
40
+ ## Export and diagnostic boundary
41
+
42
+ A resolved scene contains native semantic objects, boxes, text lines and connector
43
+ points. Exporters can inspect these without parsing SVG. Derived geometry is not
44
+ written back into the canonical artifact. No persisted scene cache is implemented.
45
+
46
+ CLI PNG exports are 2x, capped at 32 million pixels. Browser exports clamp to an
47
+ 8192px edge and 32 million pixels to respect canvas limits; SVG remains scalable.
@@ -0,0 +1,30 @@
1
+ # ADR 003: General composition and organizational design systems
2
+
3
+ Status: accepted, 2026-09-20.
4
+
5
+ ## Evidence and scope
6
+
7
+ The original MVP was validated before this phase (14 tests, production build, real editor round trip). Its shared renderer, ELK hierarchy, native patch model, and React Flow interaction layer remain useful. Closed `type` and `kind` enums, fixed card geometry, and two palettes unnecessarily limited agents.
8
+
9
+ We reviewed [C4's levels](https://c4model.com/diagrams), [Mermaid's ER conventions](https://mermaid.js.org/syntax/entityRelationshipDiagram.html), its [sequence](https://mermaid.js.org/syntax/sequenceDiagram.html) and [timeline](https://mermaid.js.org/syntax/timeline.html) references, and [Graphviz layout families](https://graphviz.org/docs/layouts/). C4 emphasizes audience and abstraction level; ER diagrams need explicit cardinality and attribute conventions. Different layout families solve different geometry problems. None implies that a document must select a diagram category.
10
+
11
+ The initial skills cover systems, process decisions, organization, data models, phase timelines, and concept maps. This is a deliberate coverage choice across different professional communication tasks, not a measured popularity ranking. Sequence lifelines and calibrated time axes deserve further work rather than a superficial claim of full support.
12
+
13
+ ## Decisions
14
+
15
+ - Version 2 keeps nodes, directed relationships, nested groups, and stable IDs. `kind` is open domain vocabulary; `type` is optional descriptive metadata and never a renderer dispatch key. No category-specific geometry implementation is added.
16
+ - Retain ELK for inferred layered geometry. Add a content-sized composition grid, general geometric shapes, straight connectors, and explicit cardinal-side ports. Grid cells encode relative composition; measured text and design-system spacing determine pixels. Human absolute pins still win.
17
+ - General shapes are rectangles, pills, diamonds, ellipses, cylinders, and text annotations. There is no raw SVG/path/CSS injection or illustration canvas. Legacy v1 cards retain their original renderer.
18
+ - Use a data-only, strict style vocabulary. Cascade: built-in fallback → system element defaults → named system role → element style → human node override. `roles` are arbitrary reusable visual conventions, not engine-defined semantic types. A role like `caution` can be reinterpreted by another organization.
19
+ - A design system has its own version, stable ID, human name, revision, canvas treatment, spacing, node/edge/group defaults, and role styles. Standalone JSON files are reusable; applying one copies a snapshot into the native document. This makes a file portable and deterministic without a filesystem resolver or network dependency. Updating the organization file does not silently change existing diagrams: reapply deliberately.
20
+ - Node/group `style` and edge `appearance` stay separate from their semantic labels/relationships. Legacy edge `style: solid|dashed` remains compatible. Arbitrary semantic kinds leave a future component library room to map meaning to visual conventions without changing identity.
21
+ - The editor patches the same validated artifact. Advanced supported fields are preserved even when controls are absent; source editing exposes the full format. There is no agent-only representation.
22
+ - Patches merge element style properties and retain omitted fields. Migration is explicit for existing files; use of v2 features through a patch upgrades the version. Unknown schema fields and future versions fail rather than disappear.
23
+
24
+ ## Composition evaluation
25
+
26
+ The first custom comparison proved grid-based fan-out/fan-in without a bespoke diagram type. Reviewing exports revealed a retry crossing, a low-contrast override after restyling, and excess whitespace in short diagrams. Side ports, coherent foreground/background overrides, and content-fit v2 bounds resolved those cases. Direct connectors now use shape intersections and diagonal collision detection. The representative gallery has no inspection warnings; the visual review remains separate from that metric.
27
+
28
+ ## Tradeoffs
29
+
30
+ IBM Plex Sans is bundled and measured exactly. Other font family names are preserved and rendered through available fallback, but are not portable until a future font packaging mechanism; inspection warns about this. Shape styles are typed but context-specific: marker/routing properties affect edges; geometry and text sizing affect nodes. Group headings retain fixed size to keep containment reliable. No crow's-foot markers, reusable object definitions, external asset loading, collaborative backend, or additional exporter formats are claimed in this phase.
@@ -0,0 +1,33 @@
1
+ # ADR 004: Local usability and distribution
2
+
3
+ Status: accepted, 2026-09-20.
4
+
5
+ The graphical editor needed a discoverable library, reusable user-defined design systems, and a setup path that does not assume the user knows the repository. These take priority over organizational authentication.
6
+
7
+ ## Local storage adapter
8
+
9
+ `forma serve --directory PATH` starts an optional Node HTTP adapter on loopback. It creates the chosen folder if missing and serves the prebuilt editor. The default directory is `~/Forma`. The library recursively lists `.forma.json` files and subfolders; users can switch to another absolute source directory in the UI. Files remain ordinary Git-friendly documents readable by any agent.
10
+
11
+ Save is explicit. Browser recovery is labeled as a draft rather than falsely claiming it has been saved to disk. Opening another library file starts a fresh undo history. Unsaved edits require a save-or-open-without-saving choice. Export always downloads a separate artifact and does not change the native file binding.
12
+
13
+ The adapter validates documents, rejects path traversal and symlinks, bounds file/request sizes, serializes its own mutations, and compares file-content revisions before overwriting. This is optimistic conflict detection, not a cross-process database transaction: external tools should still reread immediately before patching. A changed workspace ID rejects stale tab writes after a source-directory switch. Requests use a same-origin custom-header check, strict loopback Host validation, and no CORS permission. This server has no authentication and is deliberately **not an internet-facing self-hosted service**.
14
+
15
+ A static deployment keeps a browser-only library with virtual subfolder paths and import/download workflow. It cannot read arbitrary local folders without the optional local adapter. Browser libraries and saved visual identities are convenience storage, not backups.
16
+
17
+ ## Exports
18
+
19
+ The previous export path fetched fonts at click time and retained rejected fetch promises. A stopped server or failed request could therefore repeatedly produce a fetch error. Fonts are now embedded in the already-loaded application bundle, removing that runtime request and preserving deterministic SVG/PNG exports. The library may be unavailable if its process is stopped; rendering and export of the open scene continue locally.
20
+
21
+ ## Custom visual identities
22
+
23
+ A dedicated editor provides palette, typography size, corner, and spacing controls, plus validated full JSON for the complete style vocabulary and roles. Users can save a design in their browser, reuse it, import it, or export a standalone JSON for agents/Git. Applying it retains element overrides. No new canonical schema is needed.
24
+
25
+ ## Distribution
26
+
27
+ Start with GitHub source and a versioned npm-installable package containing the built static editor, CLI, core, bundled fonts/licenses, examples, and repository skills. Runtime `tsx` belongs in production dependencies because the current package exposes TypeScript source. `npm install -g forma-diagrams` provides `forma`; `forma serve` provides the editor without a build step for users. GitHub releases still attach a checksummed tarball. Validate an actual packed install before publishing. Homebrew is not required for this path.
28
+
29
+ **2026-09-21:** The npm registry package `forma-diagrams` is the primary CLI/`forma host` install. GitHub Actions publishes it from a GitHub release with provenance. Vercel and Docker continue to deploy git trees or images.
30
+
31
+ ## Later: organizational hosting
32
+
33
+ Google sign-in and per-user server-side diagram storage are deliberately deferred. That mode needs OAuth/OIDC authorization-code flow, secure sessions, explicit user/workspace authorization, ownership checks on every file operation, storage quotas, backups, and conflict handling. The organization should operate and configure its own Google client and host. The shared core/document and storage-adapter boundary allow this without adding accounts or cloud dependencies to local mode. Do not expose the local adapter as a shortcut to that product.
@@ -0,0 +1,33 @@
1
+ # ADR 005: Optional organizational hosting
2
+
3
+ Status: accepted, 2026-09-21.
4
+
5
+ ## Boundary and scope
6
+
7
+ `forma host` is an explicitly configured, authenticated deployment mode. `forma serve` remains a loopback local-folder adapter; static hosting remains account-free. Authentication and storage do not enter the rendering core or native document format. Version 1 and 2 diagrams continue to work unchanged.
8
+
9
+ This first hosted release provides private libraries for individual users of one organization. Shared folders, team ACLs, real-time collaboration, and public links are outside this release. File export/import remains available. Direct agent access uses user-issued tokens described in [ADR 006](006-hosted-agent-access.md).
10
+
11
+ ## Google identity
12
+
13
+ Reuse Google's maintained `google-auth-library` (Apache-2.0), now developed in the [google-cloud-node repository](https://github.com/googleapis/google-cloud-node/tree/main/core/packages/google-auth-library-nodejs). Use the authorization-code flow with S256 PKCE, state bound to an HttpOnly browser cookie, single-use short-lived login attempts, and an independently checked nonce. The library verifies ID-token signatures, issuer, audience, and expiration. Only OpenID/email/profile scopes are requested; no Google access or refresh tokens are persisted.
14
+
15
+ Admission requires an explicit email allowlist or a Google Workspace domain allowlist. Domain admission checks the verified `hd` claim, not the suffix of an email address or the authorization request's domain hint. Require verified email in both cases. The stable Google `sub` identifies ownership; email is a display/admission property, not a filesystem path. These choices follow [Google's OpenID Connect guidance](https://developers.google.com/identity/openid-connect/openid-connect).
16
+
17
+ Opaque random session tokens live in HttpOnly, SameSite=Lax cookies, with Secure and the `__Host-` prefix on HTTPS deployments. The server keeps only token hashes, expires sessions after 12 hours, and revokes them on sign-out. Sessions intentionally do not survive restart. Login attempts and session maps are bounded. The allowlist is applied to every authenticated request. Changes to environment configuration require restart, which also revokes all sessions.
18
+
19
+ ## Storage and isolation
20
+
21
+ Extract the existing local library implementation into a shared file adapter. Every hosted operation derives its root from the authenticated Google subject, never from a submitted user ID or directory. Data is stored under `users/SHA256(google:SUB)/diagrams/`; an administrator-only `identity.json` maps the directory to the identity. Each library retains nested files, document validation, atomic replacement, optimistic content revisions, and path/symlink containment checks. The host API never exposes absolute data paths or a directory-switch operation.
22
+
23
+ Default limits are 100 MB and 1,000 files per user, 5 MB per document, and 500 admitted user directories. Files and owner directories are created with restrictive modes. Administrators own backups and retention. A data-directory lock rejects concurrent hosted processes; this is a single-instance, local-filesystem deployment. It is not a distributed store or a cross-process transactional system. Operators must stop the process before restore/migration and confirm no process is running before removing a crash-left lock.
24
+
25
+ ## Browser and HTTP boundary
26
+
27
+ Hosted sessions gate mounting the editor. Hosted diagram drafts and active-file bindings are not persisted in browser storage; private diagrams are read from the server Library after reload. Design presets are session-only, while applied identities are preserved in saved diagram documents. Logout broadcasts to other tabs, and restored back-forward-cache pages reload their authentication state. Unsaved drafts require an explicit sign-out warning. This avoids displaying a previous account's recovered diagrams to another user of the same browser.
28
+
29
+ The HTTP service enforces a configured canonical Host and Origin, custom-header JSON writes, no CORS, bounded bodies, and a content policy that disallows remote scripts, object embedding, and framing. HTTPS is required except for loopback development. Reverse proxies preserve the public Host; forwarded headers are not trusted to establish identity or the canonical origin. Production cookies are never sent over HTTP. A Caddy/Docker Compose recipe provides TLS termination without publishing Forma's internal port.
30
+
31
+ ## Validation and remaining deployment work
32
+
33
+ Authentication is injected at a small TypeScript interface for deterministic tests. `forma host` always uses Google: there is no environment-configurable fake provider, test account, or bypass route. The test preview is excluded from npm release packages and carries a visible simulated-provider label. Automated tests cover identity admission, nonce/state failures, isolation, revision conflicts, quotas, expiry, and logout. Browser tests exercise real HTTP sessions and file operations using the simulated provider. A production operator must still configure and exercise their real Google OAuth client, consent screen, domain, and TLS deployment.
@@ -0,0 +1,25 @@
1
+ # ADR 006: Hosted agent access
2
+
3
+ Status: accepted, 2026-09-21.
4
+
5
+ ## Boundary
6
+
7
+ Hosted agents authenticate with user-issued bearer tokens, never Google cookies or OAuth client secrets. A token belongs to one admitted Google identity, inherits that user’s private library, and may further restrict folder prefix and read/write permission. The same authorization layer serves REST, `forma remote`, stdio MCP, and optional Streamable HTTP MCP. MCP is an adapter, not a security boundary.
8
+
9
+ Local `forma serve` and static deployments are unchanged. They keep using files on disk or in the browser. No token is required, and `/mcp` is not part of those modes.
10
+
11
+ ## Tokens
12
+
13
+ The signed-in editor creates tokens through `/api/tokens` using the existing session, origin, and custom-header checks. The secret is shown once. Only SHA-256 digests are stored in `agent-tokens.json` at the data-directory root, with restrictive file modes. Tokens expire in 1–90 days, can be revoked immediately, and are rejected if the owner is later removed from the admission allowlist. Folder scope is a relative prefix; path checks reject empty, hidden, parent, and absolute segments before the library adapter runs.
14
+
15
+ Agent HTTP routes (`/api/agent/*` and `/mcp`) require `Authorization: Bearer`. A session cookie is not accepted as an agent credential, and an agent token cannot list or revoke tokens.
16
+
17
+ ## CLI and MCP
18
+
19
+ `forma remote` talks to the hosted origin from `FORMA_REMOTE_URL`. The token comes from `FORMA_AGENT_TOKEN` or a private `FORMA_AGENT_TOKEN_FILE` (regular file, ≤4 KB, mode `0600` on Unix). Pull writes a `.forma-remote.json` sidecar binding server, owner, path, and content revision. Push refuses to cross that binding and uses the same optimistic revision check as human saves.
20
+
21
+ `forma mcp` is a local stdio bridge over that HTTP API. Streamable HTTP MCP at `/mcp` is off unless `FORMA_ENABLE_MCP=true`. This release uses configured bearer tokens rather than MCP OAuth discovery.
22
+
23
+ ## Remaining work
24
+
25
+ OAuth for MCP clients, shared team folders, and per-diagram ACLs remain out of scope. Operators still supply Google identity, TLS, backups (including `agent-tokens.json`), and edge rate limiting.
package/docs/format.md ADDED
@@ -0,0 +1,132 @@
1
+ # Native documents, composition, and styling
2
+
3
+ The canonical artifact is UTF-8 JSON (`name.forma.json`). Version 2 supports general diagrams. Version 1 files remain readable and preserve the original card presentation. Use `forma migrate input.forma.json --output upgraded.forma.json` for an explicit lossless upgrade. A patch introducing v2 capabilities upgrades the version automatically. Unknown fields and unsupported versions are rejected; no editor silently drops them.
4
+
5
+ IDs start with a letter, then letters/digits/`_`/`-`, up to 80 characters. IDs are globally unique. References and group cycles are validated. See [v2 schema](../schema/forma.v2.schema.json), [frozen v1 schema](../schema/forma.v1.schema.json), and [design-system schema](../schema/design-system.v1.schema.json).
6
+
7
+ ```json
8
+ {
9
+ "version": 2,
10
+ "title": "Request path",
11
+ "nodes": [
12
+ { "id": "client", "label": "Customer portal", "kind": "client" },
13
+ { "id": "api", "label": "API", "kind": "service", "role": "emphasis" }
14
+ ],
15
+ "edges": [{ "id": "request", "source": "client", "target": "api", "label": "HTTPS" }]
16
+ }
17
+ ```
18
+
19
+ No diagram type is required. `type` is optional descriptive metadata, not a layout selector. Node `kind` is arbitrary domain vocabulary; it does not restrict shapes. Nodes can have descriptions and a `group` ID. Groups have labels and optional parent IDs. `role` on a node, edge, or group selects a reusable convention from the active design system. Legacy `emphasis`, group accent `color`, edge `style: solid|dashed`, and base themes remain available.
20
+
21
+ ## General composition
22
+
23
+ `layout.direction` is RIGHT or DOWN; `spacing` is comfortable or compact. With `mode: layered` (default), relationships drive ELK and the composition/routing refinements. The engine measures typography, sizes shapes, wraps text, fits groups, routes edges, and places edge labels.
24
+
25
+ Use `layout.mode: grid` when relative placement expresses meaning. Each node can have `placement: {column: 0, row: 1}`. Occupied columns and rows size to their widest/tallest member, with system spacing between them. Indices establish order, not physical units; unused indices do not reserve empty tracks. Each cell holds one node. Omitted placements use the node's array index as column and row zero. Groups fit their descendants after placement. This supports comparisons, parallel streams, phase sequences, and custom explanations without adding categories.
26
+
27
+ `presentation.nodes[id].position: {x,y}` is an absolute human pin in scene coordinates. Pins override automatic/grid positions. Adding content around pins can conflict; inspect after editing. Clearing just a position returns that node to engine control without erasing its style. The editor and `forma align` write pins when you align or distribute components. Arrow keys nudge selected components (Shift for 8px).
28
+
29
+ ## Design systems
30
+
31
+ Apply a versioned identity:
32
+
33
+ ```sh
34
+ node packages/cli/bin.mjs style diagram.forma.json --system examples/design-systems/atelier.json
35
+ ```
36
+
37
+ Standalone systems contain `version: 1`, `id`, `name`, `revision`, and optional `canvas`, `spacing`, `node`, `edge`, `group`, and `roles`. The CLI validates then embeds the system under `presentation.designSystem`. Files are portable snapshots with no external resolution or service dependency. Edit/reuse the system JSON in Git; reapply a revision deliberately. The editor offers Atelier and Signal and preserves imported custom systems. Source editing exposes every supported property.
38
+
39
+ Cascade, from least to most specific:
40
+
41
+ 1. Built-in theme defaults.
42
+ 2. Design-system node/edge/group defaults.
43
+ 3. Design-system `roles[role]`.
44
+ 4. Node/group `style` or edge `appearance`.
45
+ 5. Human node override `presentation.nodes[id].style`.
46
+
47
+ Content and relationships remain intact when changing identity. Use coherent foreground/background overrides when an element must retain a fixed color across light and dark systems.
48
+
49
+ ## Style vocabulary
50
+
51
+ | Context | Properties |
52
+ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
53
+ | Nodes | `shape`: rect, pill, diamond, ellipse, cylinder, text; `fill`, `stroke`, `strokeWidth`, `dash`, `radius`, `opacity`, `text`, `secondary`, `fontFamily`, `fontSize`, `fontWeight` (400/600), `padding`, `width`, `height`, `align` (left/center/right), `verticalAlign` (top/middle/bottom) |
54
+ | Edges | `stroke`, `strokeWidth`, `dash`, `opacity`, `arrowStart`, `arrowEnd` (none/open/filled/diamond/circle), `routing` (orthogonal/straight), `sourcePort`, `targetPort` (top/right/bottom/left), `sourceIndex`, `targetIndex`; `fill` and `text` color the label plate and text |
55
+ | Groups | `fill`, `stroke`, `strokeWidth`, `dash`, `radius`, `opacity`, `text`, `fontFamily`; heading geometry stays fixed |
56
+ | Canvas | `background`, `text`, `secondary`, `border`, `fontFamily` |
57
+ | Spacing | `node` and `layer`, in scene pixels; override the legacy spacing preset |
58
+
59
+ `presentation.footer` changes the sheet footer. `hidden: true` removes the rule and both captions and the space they occupy. `label` replaces the left caption (default `FORMA / TYPE`). `detail` replaces the right caption (default component and relationship counts). Omit a caption, or patch it to `null`, to restore that default.
60
+
61
+ Paint values are hex colors, `none`, or `transparent`. Dash is solid/dashed/dotted. Width is explicit; height is a minimum that grows to avoid clipping text. Pills, diamonds, and ellipses center their content unless `align` or `verticalAlign` says otherwise. Plain text nodes omit the surrounding shape and are useful for diagram annotations. All values are bounded and data-only: no CSS, remote URLs, or raw SVG fragments.
62
+
63
+ ## Connection points and paths
64
+
65
+ Every shape has one connection point on each side, centered. Add more on a side when several connectors should leave from different places:
66
+
67
+ ```json
68
+ { "id": "api", "label": "API", "ports": { "right": 3, "left": 2 } }
69
+ ```
70
+
71
+ Counts are 1–12. Omitted sides stay at one. Points are spaced evenly: index 0 is nearest the start of the side (left or top), and the default index is the center point. An edge selects a point with `appearance.sourcePort` / `targetPort` and optional `sourceIndex` / `targetIndex`. Several edges may share one point. Shared horizontal or vertical runs are allowed, so those connectors read as one combined line. The router still prefers a path that does not cross a different connector.
72
+
73
+ Set `path` on an edge to pin the corners it must pass through. Coordinates are scene points, same as a human pin. The line stays orthogonal: Forma inserts elbows between corners and keeps the ends on the chosen points. A pinned path is not rerouted when components move. Clear it with a patch of `"path": null` to return to automatic routing.
74
+
75
+ ```json
76
+ {
77
+ "id": "retry",
78
+ "source": "gate",
79
+ "target": "checks",
80
+ "appearance": {
81
+ "sourcePort": "right",
82
+ "sourceIndex": 0,
83
+ "targetPort": "right",
84
+ "targetIndex": 1
85
+ },
86
+ "path": [
87
+ { "x": 640, "y": 180 },
88
+ { "x": 640, "y": 40 }
89
+ ]
90
+ }
91
+ ```
92
+
93
+ In the editor, hover a component to use its points, double-click a line or use Add corner to pin a path, and drag a corner to reshape it. Shift-click adds to the selection; a marquee does too.
94
+
95
+ IBM Plex Sans Regular and SemiBold are bundled, measured, and embedded in exported SVG. Other font families are accepted but may fall back differently across machines; inspection reports that limitation. Portable custom-font packaging is future work. Straight routes attach to shape boundaries and do not avoid obstacles; inspect them. Side ports apply to orthogonal routes. Human pins preserve placement, not a frozen route.
96
+
97
+ Example customization:
98
+
99
+ ```json
100
+ {
101
+ "nodes": [
102
+ {
103
+ "id": "api",
104
+ "style": {
105
+ "fill": "#fff0be",
106
+ "stroke": "#454545",
107
+ "text": "#243b30",
108
+ "dash": "dashed",
109
+ "radius": 0,
110
+ "fontFamily": "IBM Plex Sans"
111
+ }
112
+ }
113
+ ],
114
+ "edges": [{ "id": "request", "appearance": { "dash": "dotted", "arrowEnd": "open" } }]
115
+ }
116
+ ```
117
+
118
+ ## Transactional patches
119
+
120
+ `patch --patch changes.json` merges arrays by stable ID. Omitted fields survive; new objects need all required fields. Style/appearance properties merge individually. A `designSystem` patch replaces the complete system snapshot; null removes it. Layout fields merge. Top-level title, description, theme, nodes, edges, groups, overrides, and remove are supported.
121
+
122
+ ```json
123
+ {
124
+ "nodes": [{ "id": "api", "label": "Public API" }],
125
+ "overrides": { "api": { "style": { "fill": "#eef4ff" }, "position": null } },
126
+ "remove": { "nodes": ["obsolete"] }
127
+ }
128
+ ```
129
+
130
+ Null removes an entire node override or an override's position, color, or style. To remove one optional element style property or semantic field, edit the native JSON and validate; omission in a patch means preserve. Removing a node removes incident edges and its overrides. Removing a group releases its children. Files are written atomically only after full validation. Serialization sorts object keys while preserving array order for useful Git diffs.
131
+
132
+ `layout --output scene.json` writes derived measured geometry and resolved styles. A scene is not a persistence format. Do not replace native documents with scene JSON or SVG; those omit editable intent. Both CLI and editor always recompute a scene from the native artifact.
@@ -0,0 +1,73 @@
1
+ # One engine, many compositions
2
+
3
+ These are actual PNG exports of native v2 documents. Open any `.forma.json` file in the editor, or use `node packages/cli/bin.mjs render FILE --output FILE.png`. The editor's template dialog also offers these starting points. None requires a category in the document model.
4
+
5
+ ## A custom explanation, without a custom diagram type
6
+
7
+ The left stream is deliberately narrow. On the right, work fans out into parallel generation and converges on a visually emphasized verification gate. Grid cells encode the comparison; the engine sizes text, fits groups, and derives connectors. The pale yellow gate has a dashed charcoal outline, square corners, IBM Plex Sans text, and a dotted open-arrow connector.
8
+
9
+ [Native artifact](../examples/gallery/development.forma.json)
10
+
11
+ ![Parallel development comparison](images/gallery/development.png)
12
+
13
+ The **same content, relationships, grouping, placement, and element overrides** adopts Signal's dark visual identity. The identity changes canvas, palette, geometry, role treatments, and connector appearance. The fixed yellow gate keeps its explicit foreground and background colors.
14
+
15
+ [Signal artifact](../examples/gallery/development-signal.forma.json) · [Reusable design systems](../examples/design-systems)
16
+
17
+ ![Same diagram with Signal identity](images/gallery/development-signal.png)
18
+
19
+ ## Conventional diagrams using general primitives
20
+
21
+ ### System architecture
22
+
23
+ [Artifact](../examples/gallery/architecture.forma.json) · [Composition skill](../skills/forma-architecture/SKILL.md)
24
+
25
+ ![Architecture](images/gallery/architecture.png)
26
+
27
+ ### Decisions and feedback
28
+
29
+ The normal path stays in one grid column. Side ports put the retry connector beside it. The first render crossed the retry path; visual inspection drove this refinement.
30
+
31
+ [Artifact](../examples/gallery/decision.forma.json) · [Composition skill](../skills/forma-flow/SKILL.md)
32
+
33
+ ![Decision flow](images/gallery/decision.png)
34
+
35
+ ### Organization
36
+
37
+ Automatic layout, semantic emphasis, one width override, and an arrowless system connector convention. The organizational skill supplies hierarchy and labeling guidance; individual nodes do not specify colors, fonts, or coordinates.
38
+
39
+ [Artifact](../examples/gallery/organization.forma.json) · [Composition skill](../skills/forma-organization/SKILL.md)
40
+
41
+ ![Organization](images/gallery/organization.png)
42
+
43
+ ### Entities and relationships
44
+
45
+ Cardinalities are explicit text; this is not a claim of native crow's-foot notation.
46
+
47
+ [Artifact](../examples/gallery/entities.forma.json) · [Composition skill](../skills/forma-entities/SKILL.md)
48
+
49
+ ![Entities](images/gallery/entities.png)
50
+
51
+ ### A phase timeline
52
+
53
+ Equal grid spacing represents order, not elapsed time; the description makes that distinction explicit.
54
+
55
+ [Artifact](../examples/gallery/timeline.forma.json) · [Composition skill](../skills/forma-timeline/SKILL.md)
56
+
57
+ ![Timeline](images/gallery/timeline.png)
58
+
59
+ ### A balanced concept map
60
+
61
+ An ellipse, rectangles, a relative grid, and arrowless direct relationships. No dedicated mind-map renderer.
62
+
63
+ [Artifact](../examples/gallery/mindmap.forma.json) · [Composition skill](../skills/forma-mindmap/SKILL.md)
64
+
65
+ ![Mind map](images/gallery/mindmap.png)
66
+
67
+ ## Human → agent continuation
68
+
69
+ The styled custom example was opened in the graphical editor. Its verification gate was renamed, recolored, and dragged. The downloaded artifact was then patched through the CLI and reopened. The human pin and styles survived.
70
+
71
+ [Human-saved file](../examples/roundtrip/human-edited.forma.json) · [Agent patch](../examples/roundtrip/agent-patch.json) · [Result](../examples/roundtrip/agent-continued.forma.json) · [Verification details](verification-v2.md)
72
+
73
+ ![Editor after the round trip](images/gallery/editor-roundtrip.png)
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,50 @@
1
+ # Install and use Forma
2
+
3
+ Requires Node.js 22+ and npm. Start with the released package:
4
+
5
+ ```sh
6
+ npm install -g https://github.com/joeycast/forma/releases/download/v0.5.2/forma-diagrams-0.5.2.tgz
7
+ forma serve --directory ~/Forma
8
+ ```
9
+
10
+ Open the printed URL (normally http://127.0.0.1:4242). Keep the terminal process running. Forma creates the folder when needed. Stop with Ctrl+C. Use `--port 4243` if the default port is occupied. No account or external service is needed.
11
+
12
+ If global npm installation requires administrator privileges, use your normal user-owned Node installation or install to a user-owned prefix; do not blindly run npm with sudo.
13
+
14
+ ## Your diagrams
15
+
16
+ Open **Library** to browse diagrams, search, filter subfolders, refresh agent changes, or change the source folder. Save with **Save** or Cmd/Ctrl+S. The first save asks for a relative path such as `architecture/checkout.forma.json`; subfolders are created. **Export → Forma document** downloads a copy, while **Save** writes back to the library.
17
+
18
+ If an agent changes a file after you opened it, a conflicting save is rejected and your edits remain in the editor. Reopen the disk version or use a new save path to retain both. Refresh the library to see new agent-created files. The app remembers the active file within the tab session; after restarting the server, reopen it from Library to establish a fresh file revision.
19
+
20
+ Browser-only/static-hosted Forma has a separate library stored in that browser. It supports path-based organization, but does not read local disk folders. Export native files to back it up or move to a local folder.
21
+
22
+ ## Give your agent the setup prompt
23
+
24
+ Choose **Agent guide → Copy setup prompt**. The prompt explains installation, the shared folder, how to locate the bundled skill, the CLI workflow, and preservation of human edits. No particular model or harness is assumed.
25
+
26
+ The skill is at `$(npm root -g)/forma-diagrams/skills/forma/SKILL.md` after a global installation. Specialized skills are alongside it. The package README and document reference explain the same interfaces for other integrations.
27
+
28
+ ## Define your visual identity
29
+
30
+ Choose **Create or edit design system** in the Design inspector. Set your palette, corners, typography size, and spacing. Advanced JSON exposes roles and all supported properties. Give the design a unique identity ID, save/apply it, and export its JSON for your agents:
31
+
32
+ ```sh
33
+ forma style ~/Forma/architecture/checkout.forma.json --system company.design.json
34
+ ```
35
+
36
+ Element-level overrides take precedence. Only IBM Plex Sans is bundled for deterministic font metrics; custom font packaging remains future work.
37
+
38
+ ## Source installation and updates
39
+
40
+ ```sh
41
+ git clone https://github.com/joeycast/forma.git
42
+ cd forma
43
+ npm ci
44
+ npm run build
45
+ node packages/cli/bin.mjs serve --directory ~/Forma
46
+ ```
47
+
48
+ Install a newer release tarball to update. The diagrams directory is separate from the application and is not replaced. Each GitHub [release](https://github.com/joeycast/forma/releases) includes a checksum file. The npm registry package `forma-diagrams` is not published yet; do not install similarly named packages. Hosted instances (Node, Docker, Vercel) follow [Updating a hosted instance](self-hosting.md#updating-a-hosted-instance).
49
+
50
+ Static hosting of `dist/` remains supported. Google sign-in, private server-side user libraries, and optional agent tokens are available through the separate authenticated `forma host` mode; see [self-hosting](self-hosting.md). The local `serve` command only binds to loopback and must not be reverse-proxied onto the internet.
@@ -0,0 +1,24 @@
1
+ # Forma MVP implementation plan
2
+
3
+ Goal: prove semantic creation → professional composition → human edit → agent
4
+ patch preserving edits → local SVG/PNG export.
5
+
6
+ Architecture: shared TypeScript engine; ELK layout; React Flow editor; Node CLI.
7
+ File format: versioned JSON with stable IDs and separate presentation overrides.
8
+
9
+ - [x] Implement document schema, validation, deterministic serialization and
10
+ transactional ID-based patches in packages/core/src/document.ts. Test duplicate
11
+ IDs, invalid endpoints, hierarchy cycles, unrecognized versions and pin survival.
12
+ - [x] Implement text measurement, themes, hierarchical ELK layout, human override
13
+ reconciliation, routing and scene diagnostics. Test both examples plus branch,
14
+ cycle, long-label, negative-pin and overlap fixtures.
15
+ - [x] Implement shared SVG node/edge/group rendering and exporter interface.
16
+ Package a local font; verify XML escaping and no remote assets.
17
+ - [x] Implement create/validate/patch/layout/render/inspect/export CLI commands,
18
+ machine-readable results and nonzero error exits. Exercise actual files.
19
+ - [x] Build static editor with outline, inspector, pan/zoom, dragging, creating and
20
+ deleting nodes/edges, undo/redo, themes, layout, JSON import/save and SVG/PNG.
21
+ - [x] Run software, visually inspect diagrams and editor, exercise human edits,
22
+ download the artifact, agent-patch it and inspect/export the result.
23
+ - [x] Document API, format, architecture, CLI and known limits; add concise agent
24
+ skill and contributor instructions. Run full build/tests and static preview.