@echelon-foundry/design-system 0.3.0 → 0.4.0-main.222.1
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.
- package/README.md +29 -0
- package/contracts/diagram-presentation.json +109 -0
- package/contracts/machine-operability.json +48 -0
- package/contracts/workflow-capabilities.json +25 -0
- package/dist/all.css +175 -3
- package/dist/build-metrics.json +33 -5
- package/dist/components.css +173 -3
- package/dist/foundations.css +2 -0
- package/dist/marketing/forma-all.css +176 -4
- package/dist/marketing/forma-echelon-marketing.css +3 -1
- package/dist/marketing/forma-marketing-theme-echelon.css +1 -1
- package/dist/marketing/forma-marketing.css +3 -1
- package/dist/marketing/forma-marketing.manifest.json +9 -9
- package/dist/marketing/forma-marketing.sha256 +4 -4
- package/dist/patterns/bounded-overflow.html +9 -0
- package/dist/patterns/button.html +5 -0
- package/dist/patterns/container.html +6 -0
- package/dist/patterns/date-time-field.html +5 -0
- package/dist/patterns/grid.html +5 -0
- package/dist/patterns/image-choice.html +2 -2
- package/dist/patterns/key-value-list.html +5 -0
- package/dist/patterns/section.html +4 -0
- package/dist/patterns/skip-link.html +6 -0
- package/dist/patterns/spatial-canvas.html +2 -2
- package/dist/patterns/split.html +12 -0
- package/dist/patterns/surface.html +4 -0
- package/dist/patterns/text-field.html +5 -0
- package/dist/patterns/text-roles.html +7 -0
- package/dist/patterns/textarea.html +5 -0
- package/dist/patterns/visually-hidden.html +4 -0
- package/docs/MARKETING-SITES.md +1 -1
- package/docs/workflow/EMBEDDING.md +148 -0
- package/docs/workflow/EXTERNAL-PRODUCERS.md +34 -0
- package/docs/workflow/FORMAT.md +196 -0
- package/docs/workflow/RENDERING.md +90 -0
- package/docs/workflow/REQUIREMENTS-AUDIT-2026-10-01.md +55 -0
- package/docs/workflow/VALIDATION.md +91 -0
- package/examples/workflows/fixtures.json +101 -0
- package/examples/workflows/forma/workflows/accessible.forma-workflow.json +90 -0
- package/examples/workflows/forma/workflows/branching.forma-workflow.json +91 -0
- package/examples/workflows/forma/workflows/branded.forma-workflow.json +85 -0
- package/examples/workflows/forma/workflows/color-mapping.forma-workflow.json +111 -0
- package/examples/workflows/forma/workflows/colored.forma-workflow.json +131 -0
- package/examples/workflows/forma/workflows/edge-metadata.forma-workflow.json +66 -0
- package/examples/workflows/forma/workflows/embedded-edit.forma-workflow.json +117 -0
- package/examples/workflows/forma/workflows/embedded-view.forma-workflow.json +93 -0
- package/examples/workflows/forma/workflows/extensions.forma-workflow.json +90 -0
- package/examples/workflows/forma/workflows/groups.forma-workflow.json +89 -0
- package/examples/workflows/forma/workflows/interactive-export.forma-workflow.json +62 -0
- package/examples/workflows/forma/workflows/minimal.forma-workflow.json +17 -0
- package/examples/workflows/forma/workflows/mobile.forma-workflow.json +80 -0
- package/examples/workflows/forma/workflows/multi-step.forma-workflow.json +82 -0
- package/examples/workflows/forma/workflows/no-layout.forma-workflow.json +90 -0
- package/examples/workflows/forma/workflows/node-metadata.forma-workflow.json +80 -0
- package/examples/workflows/forma/workflows/runtime-ports.forma-workflow.json +118 -0
- package/examples/workflows/forma/workflows/static-export.forma-workflow.json +77 -0
- package/examples/workflows/forma/workflows/swimlanes.forma-workflow.json +130 -0
- package/figma/component-contracts.json +140 -0
- package/package.json +33 -10
- package/patterns/bounded-overflow.html +9 -0
- package/patterns/button.html +5 -0
- package/patterns/container.html +6 -0
- package/patterns/date-time-field.html +5 -0
- package/patterns/grid.html +5 -0
- package/patterns/image-choice.html +2 -2
- package/patterns/key-value-list.html +5 -0
- package/patterns/section.html +4 -0
- package/patterns/skip-link.html +6 -0
- package/patterns/spatial-canvas.html +2 -2
- package/patterns/split.html +12 -0
- package/patterns/surface.html +4 -0
- package/patterns/text-field.html +5 -0
- package/patterns/text-roles.html +7 -0
- package/patterns/textarea.html +5 -0
- package/patterns/visually-hidden.html +4 -0
- package/schemas/workflow/1.0/forma-workflow.schema.json +1045 -0
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
</header>
|
|
6
6
|
<div class="ef-spatial-canvas__viewport" tabindex="0" aria-label="Scrollable system map">
|
|
7
7
|
<div class="ef-spatial-canvas__surface">
|
|
8
|
-
<article class="ef-surface"><h3>Billing</h3><p>Selected node</p></article>
|
|
9
|
-
<article class="ef-surface"><h3>Chrona</h3><p>Time source</p></article>
|
|
8
|
+
<article class="ef-surface" id="billing"><h3>Billing</h3><p>Selected node</p></article>
|
|
9
|
+
<article class="ef-surface" id="chrona"><h3>Chrona</h3><p>Time source</p></article>
|
|
10
10
|
</div>
|
|
11
11
|
</div>
|
|
12
12
|
<details class="ef-spatial-canvas__index"><summary>Objects in this view</summary><nav aria-label="Canvas objects"><a href="#billing">Billing</a><a href="#chrona">Chrona</a></nav></details>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<div class="ef-site">
|
|
2
|
+
<div class="ef-split">
|
|
3
|
+
<div>
|
|
4
|
+
<h2>Install</h2>
|
|
5
|
+
<p>The primary region takes the available space. The supporting region wraps below it when the row runs out of room.</p>
|
|
6
|
+
</div>
|
|
7
|
+
<aside aria-label="Requirements">
|
|
8
|
+
<h3>Requirements</h3>
|
|
9
|
+
<p>Node 22 or later.</p>
|
|
10
|
+
</aside>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<div class="ef-field">
|
|
2
|
+
<label class="ef-field__label" for="text-field-email">Work email</label>
|
|
3
|
+
<span class="ef-field__description" id="text-field-email-description">We send sign-in links to this address.</span>
|
|
4
|
+
<input id="text-field-email" name="email" type="email" autocomplete="email" aria-describedby="text-field-email-description" required>
|
|
5
|
+
</div>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<div class="ef-site">
|
|
2
|
+
<p class="ef-eyebrow">Release 0.3</p>
|
|
3
|
+
<h2>Faster onboarding for new teams</h2>
|
|
4
|
+
<p class="ef-lead">A lead paragraph introduces the section in one or two sentences.</p>
|
|
5
|
+
<blockquote class="ef-statement"><p>Forma keeps behavior in the application and presentation in the design system.</p></blockquote>
|
|
6
|
+
<span class="ef-component-kicker">Component kicker</span>
|
|
7
|
+
</div>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<div class="ef-field">
|
|
2
|
+
<label class="ef-field__label" for="textarea-summary">Change summary</label>
|
|
3
|
+
<span class="ef-field__description" id="textarea-summary-description">Describe what changed and why. Plain text, up to 500 characters.</span>
|
|
4
|
+
<textarea id="textarea-summary" name="summary" rows="4" maxlength="500" aria-describedby="textarea-summary-description"></textarea>
|
|
5
|
+
</div>
|
package/docs/MARKETING-SITES.md
CHANGED
|
@@ -314,7 +314,7 @@ Keep the `@v<version>` of the Forma actions equal to the version in
|
|
|
314
314
|
3. Add it in Forma:
|
|
315
315
|
- pattern HTML;
|
|
316
316
|
- CSS in `src/marketing/` using role tokens only;
|
|
317
|
-
-
|
|
317
|
+
- a catalog entry in `catalog/components/<slug>.mjs` (see `docs/CATALOG-AUTHORING.md`);
|
|
318
318
|
- tests (structure, browser, axe, 320 px);
|
|
319
319
|
- a gap entry closed in `requirements/MARKETING-PRESENTATION.md`.
|
|
320
320
|
4. Release it. The sites then upgrade (question 7) and remove any local
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Embedding the workflow renderer and editor
|
|
2
|
+
|
|
3
|
+
`@echelon-foundry/forma-workflow` is the reusable workflow component. It
|
|
4
|
+
provides the public `<forma-workflow>` element and runs the F# engine
|
|
5
|
+
(`Forma.Workflow`) on .NET WebAssembly. It is a separate, opt-in package. The
|
|
6
|
+
design-system package stays zero-runtime (ADR-0006). You do not need Forma
|
|
7
|
+
Studio.
|
|
8
|
+
|
|
9
|
+
A complete host application that is not Studio lives in
|
|
10
|
+
[`examples/workflow-embedding/`](../../examples/workflow-embedding/). It shows
|
|
11
|
+
runtime, edit, pick and isolated modes and a partner brand. Its browser tests
|
|
12
|
+
are in [`tests/browser/workflow-embed.spec.mjs`](../../tests/browser/workflow-embed.spec.mjs).
|
|
13
|
+
|
|
14
|
+
## Same-page embedding (no iframe)
|
|
15
|
+
|
|
16
|
+
```html
|
|
17
|
+
<link rel="stylesheet" href="/assets/forma/tokens.css">
|
|
18
|
+
<link rel="stylesheet" href="/assets/forma/foundations.css">
|
|
19
|
+
<link rel="stylesheet" href="/assets/forma/components.css">
|
|
20
|
+
<link rel="stylesheet" href="/assets/forma-workflow/forma-workflow.css">
|
|
21
|
+
<script type="module" src="/assets/forma-workflow/forma-workflow.js"></script>
|
|
22
|
+
|
|
23
|
+
<forma-workflow id="designer" mode="edit"></forma-workflow>
|
|
24
|
+
<script type="module">
|
|
25
|
+
const designer = document.getElementById("designer");
|
|
26
|
+
const workflow = await (await fetch("/forma/workflows/launch.forma-workflow.json")).json();
|
|
27
|
+
await designer.load(workflow);
|
|
28
|
+
designer.addEventListener("forma-workflow-change", (e) => save(e.detail.workflow));
|
|
29
|
+
</script>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
You can also put the document inline as an inert data block. The element then
|
|
33
|
+
loads it on start, and the static figure inside the element is the
|
|
34
|
+
no-script fallback:
|
|
35
|
+
|
|
36
|
+
```html
|
|
37
|
+
<forma-workflow mode="view">
|
|
38
|
+
<script type="application/json" class="forma-workflow-source">{ "format": "forma-workflow", … }</script>
|
|
39
|
+
<figure class="ef-diagram">…static rendering…</figure>
|
|
40
|
+
</forma-workflow>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The element inherits Forma tokens, brands (`data-ef-brand` on any ancestor),
|
|
44
|
+
themes, forced colours, reduced motion and responsive behaviour from the host
|
|
45
|
+
page.
|
|
46
|
+
|
|
47
|
+
## Host modes
|
|
48
|
+
|
|
49
|
+
| `mode` | What people can do |
|
|
50
|
+
|---|---|
|
|
51
|
+
| `view` | Read. Zoom. Activate declared intents. |
|
|
52
|
+
| `inspect` | Select objects and read their metadata, references, status and extensions. |
|
|
53
|
+
| `edit` | Create, connect, reconnect, move, resize, rename and recolour. Set status, ports, groups and lanes, metadata, references, interactions and accessibility. Arrange. Undo and redo. |
|
|
54
|
+
| `pick` | Choose one or more objects; the host receives them on "Use selection". |
|
|
55
|
+
| `runtime` | Read. The host projects live state with `setRuntimeState`. The document is never changed. |
|
|
56
|
+
|
|
57
|
+
## Host contract `forma-workflow-host/1`
|
|
58
|
+
|
|
59
|
+
The contract is small and versioned. Hosts never see the engine's internal state.
|
|
60
|
+
|
|
61
|
+
**Attributes:** `mode`, `instance` (optional stable id), `engine` (optional URL of the engine directory).
|
|
62
|
+
|
|
63
|
+
**Methods** (each returns a promise):
|
|
64
|
+
|
|
65
|
+
| Method | Effect |
|
|
66
|
+
|---|---|
|
|
67
|
+
| `load(workflow)` | Load a document (object or JSON text). Emits `forma-workflow-load`. |
|
|
68
|
+
| `getWorkflow()` | The current document, canonical JSON. |
|
|
69
|
+
| `execute(command)` | Run a validated command, for example `{ name: "addNode", kind: "task", label: "Review" }`, `{ name: "connect", source: { node: "a" }, target: { node: "b" } }`, `{ name: "setLabel", object: "node:a", value: "…" }`, `{ name: "setMetadata", object: "node:a", key: "owner", value: "Ops" }`, `{ name: "setStatus", object: "node:a", status: { state: "active" } }`, `{ name: "setColor", object: "node:a", property: "fill", value: "#e8f0ff" }`, `{ name: "move", nodes: ["a"], dx: 8, dy: 0 }`, `{ name: "delete", objects: ["edge:e1"] }`, `{ name: "autoLayout" }`. |
|
|
70
|
+
| `undo()`, `redo()` | History. |
|
|
71
|
+
| `select(objects)` | `["node:a", { type: "edge", id: "e1" }]`. |
|
|
72
|
+
| `focusObject(object)` | Move keyboard focus to an object. |
|
|
73
|
+
| `setRuntimeState(states)` | `{ "nodeId": { state: "active", label: "In progress" }, "other": null }`. |
|
|
74
|
+
| `validate(workflow)` | The validation report, without loading. |
|
|
75
|
+
|
|
76
|
+
**Events** (bubbling `CustomEvent`s; `detail` is JSON):
|
|
77
|
+
|
|
78
|
+
| Event | `detail` |
|
|
79
|
+
|---|---|
|
|
80
|
+
| `forma-workflow-ready` | Engine started. |
|
|
81
|
+
| `forma-workflow-load` | `valid`, `class`, `findings`, `workflow`. |
|
|
82
|
+
| `forma-workflow-change` | `description`, `workflow` (the new document), `valid`, `class`, `findings`. Only legal, validated changes arrive. |
|
|
83
|
+
| `forma-workflow-selection` | `objects: [{ type, id }]`. |
|
|
84
|
+
| `forma-workflow-focus` | `object`. |
|
|
85
|
+
| `forma-workflow-intent` | `object`, `action`, `target`, `command`, `label`, `parameters`. The host decides what to do. |
|
|
86
|
+
| `forma-workflow-pick` | `objects`. |
|
|
87
|
+
| `forma-workflow-refused` | `message`. A command was refused and nothing changed. |
|
|
88
|
+
| `forma-workflow-error` | `message` (the engine failed to start). |
|
|
89
|
+
|
|
90
|
+
A command that would introduce a new validation error is refused. Examples are a
|
|
91
|
+
dangling connection, a port over its limit, or a blank name. Unknown metadata
|
|
92
|
+
and extensions survive every edit.
|
|
93
|
+
|
|
94
|
+
## Interaction model
|
|
95
|
+
|
|
96
|
+
- **Keyboard.** Every object is a button in the Objects list. Arrow keys move the selected steps (Shift moves further). Delete removes them. Escape cancels. All inspector fields are native form controls.
|
|
97
|
+
- **Pointer.** Click selects. Ctrl, Cmd or Shift-click adds to the selection. Drag a selected step to move it; it is one change when released. Drag the corner handle to resize. Drag the round handle onto another step to connect.
|
|
98
|
+
- **Touch and mobile.** Tap selects. No operation needs dragging: "Connect" then tap a target, the Connect form, the Move buttons and the Position and size fields all work without it. Handles have a 44px hit area. Below 64rem the layout stacks, with the canvas first, and the canvas scrolls inside its own region.
|
|
99
|
+
- **Zoom and pan.** Use the zoom buttons. Pan by scrolling the canvas. Zoom is view state and is never saved.
|
|
100
|
+
- **Announcements.** A polite live region reports each change, refusal and selection.
|
|
101
|
+
|
|
102
|
+
## .NET hosts
|
|
103
|
+
|
|
104
|
+
A host that already runs .NET, such as Forma Studio, links `Forma.Workflow`.
|
|
105
|
+
It supplies the element's transport itself instead of loading a second runtime:
|
|
106
|
+
|
|
107
|
+
```js
|
|
108
|
+
element.transport = {
|
|
109
|
+
async dispatch(json) { return myExports.WorkflowDispatch(json); }, // backed by EmbedHost.dispatch
|
|
110
|
+
async document(id) { return myExports.WorkflowDocument(id); },
|
|
111
|
+
async dispose(id) { myExports.WorkflowDispose(id); },
|
|
112
|
+
async validate(text) { return JSON.parse(myExports.WorkflowValidate(text)); }
|
|
113
|
+
};
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Isolated mode (iframe)
|
|
117
|
+
|
|
118
|
+
Use isolated mode only when you need stronger separation. It carries the same
|
|
119
|
+
portable document over the same `forma-workflow-host/1` contract, through
|
|
120
|
+
`postMessage`:
|
|
121
|
+
|
|
122
|
+
```html
|
|
123
|
+
<iframe src="/assets/forma-workflow/frame.html?parent=https://app.example.org&forma=https://app.example.org/assets/forma/&brand=echelon"></iframe>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
| From the parent | Fields |
|
|
127
|
+
|---|---|
|
|
128
|
+
| `{ protocol, id, type: "load", document }` | |
|
|
129
|
+
| `{ protocol, id, type: "mode", mode }` | |
|
|
130
|
+
| `{ protocol, id, type: "command", command }` | |
|
|
131
|
+
| `{ protocol, id, type: "select", objects }`, `"focus"`, `"runtime"`, `"undo"`, `"redo"`, `"get"` | |
|
|
132
|
+
|
|
133
|
+
| From the frame | Fields |
|
|
134
|
+
|---|---|
|
|
135
|
+
| `{ protocol, type: "frame-ready" }` | |
|
|
136
|
+
| `{ protocol, type: "reply", id, ok, workflow?, error? }` | |
|
|
137
|
+
| `{ protocol, type: "event", event: { type, … } }` | the same events as above |
|
|
138
|
+
|
|
139
|
+
The frame accepts messages only from the origin in `?parent=`, and only from
|
|
140
|
+
`window.parent`. It posts only to that origin.
|
|
141
|
+
|
|
142
|
+
## Security
|
|
143
|
+
|
|
144
|
+
Workflow files are data. The engine builds every view from a typed HTML tree
|
|
145
|
+
that escapes all text and attributes. It never emits event-handler attributes
|
|
146
|
+
or `javascript:` URLs. Intents are reported, never executed. The host element
|
|
147
|
+
has no `eval`, no `new Function` and no decision logic; a package test checks
|
|
148
|
+
this.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Producing workflows outside Forma
|
|
2
|
+
|
|
3
|
+
Any system can create a valid workflow from the published schema alone.
|
|
4
|
+
[`examples/external-producer/produce.mjs`](../../examples/external-producer/produce.mjs)
|
|
5
|
+
does it with plain Node.js and no Forma code at all.
|
|
6
|
+
[`consume.mjs`](../../examples/external-producer/consume.mjs) reads the file
|
|
7
|
+
back after other tools have edited it.
|
|
8
|
+
|
|
9
|
+
## Rules for producers
|
|
10
|
+
|
|
11
|
+
1. Write `format`, `formatVersion: "1.0.0"`, `id`, `title`, `forma.version` and `nodes`.
|
|
12
|
+
2. Give every node, edge and group a stable id. Never reuse array positions as ids.
|
|
13
|
+
3. Leave out coordinates unless you mean them. Forma lays out a workflow with no layout deterministically.
|
|
14
|
+
4. Put meaning in core fields: `kind`, `status`, `color`, `references`, group `members`. Put your own data in `metadata` or in `extensions["your.namespace"]`.
|
|
15
|
+
5. Validate before publishing: `forma-workflow validate --json your.forma-workflow.json`.
|
|
16
|
+
6. Save to `forma/workflows/<id>.forma-workflow.json`.
|
|
17
|
+
|
|
18
|
+
## Rules for consumers
|
|
19
|
+
|
|
20
|
+
- Read the core fields you understand. Keep everything else, including unknown extensions and metadata, and write it back unchanged.
|
|
21
|
+
- Check `formatVersion` and the validation class before relying on a document.
|
|
22
|
+
|
|
23
|
+
## The round-trip proof
|
|
24
|
+
|
|
25
|
+
1. An external producer writes a `.forma-workflow.json` file.
|
|
26
|
+
2. Forma validates it.
|
|
27
|
+
3. Forma Studio opens it and edits it.
|
|
28
|
+
4. Forma Studio saves it.
|
|
29
|
+
5. An external consumer reads it.
|
|
30
|
+
|
|
31
|
+
`npm run proof:external` in kemiller2002/forma-studio runs all five steps. It
|
|
32
|
+
checks that stable ids, nodes, edges, semantics, colours, metadata, unknown
|
|
33
|
+
namespaced extensions and domain references survive. The evidence is recorded
|
|
34
|
+
in that repository under `docs/evidence/`.
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# Forma portable workflow format 1.0
|
|
2
|
+
|
|
3
|
+
Status: **normative**. Requirement: [`requirements/PORTABLE-WORKFLOW-INTERCHANGE.md`](../../requirements/PORTABLE-WORKFLOW-INTERCHANGE.md).
|
|
4
|
+
Decision: [ADR-0006](../decisions/ADR-0006-portable-workflow-capability.md).
|
|
5
|
+
|
|
6
|
+
A Forma workflow is a JSON document. Any system can write one: an application,
|
|
7
|
+
an agent, a backend service, a CLI or Forma Studio. Forma validates it, lays it
|
|
8
|
+
out, renders it to HTML and edits it. You do not need Forma Studio for any of
|
|
9
|
+
these.
|
|
10
|
+
|
|
11
|
+
| Item | Value |
|
|
12
|
+
|---|---|
|
|
13
|
+
| File suffix | `.forma-workflow.json` |
|
|
14
|
+
| Repository convention | `forma/workflows/<workflow-id>.forma-workflow.json` |
|
|
15
|
+
| Format identifier | `"format": "forma-workflow"` |
|
|
16
|
+
| Format version | `"formatVersion": "1.0.0"` (semantic versioning) |
|
|
17
|
+
| JSON Schema (repository) | [`schemas/workflow/1.0/forma-workflow.schema.json`](../../schemas/workflow/1.0/forma-workflow.schema.json) |
|
|
18
|
+
| JSON Schema (package) | `@echelon-foundry/design-system/workflow/forma-workflow.schema.json` |
|
|
19
|
+
| JSON Schema (`$id`) | `https://forma.echelonfoundry.com/schemas/workflow/1.0/forma-workflow.schema.json` |
|
|
20
|
+
| Capability contract | `@echelon-foundry/design-system/contracts/workflow-capabilities.json` |
|
|
21
|
+
| Reference fixtures | [`examples/workflows/forma/workflows/`](../../examples/workflows/forma/workflows/) with [`fixtures.json`](../../examples/workflows/fixtures.json) |
|
|
22
|
+
|
|
23
|
+
## The smallest workflow
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"format": "forma-workflow",
|
|
28
|
+
"formatVersion": "1.0.0",
|
|
29
|
+
"id": "minimal",
|
|
30
|
+
"title": "Minimal workflow",
|
|
31
|
+
"forma": { "version": "0.4.0" },
|
|
32
|
+
"nodes": [ { "id": "start", "kind": "start", "label": "Start" } ]
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## A realistic workflow
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"$schema": "https://forma.echelonfoundry.com/schemas/workflow/1.0/forma-workflow.schema.json",
|
|
41
|
+
"format": "forma-workflow",
|
|
42
|
+
"formatVersion": "1.0.0",
|
|
43
|
+
"id": "launch-readiness",
|
|
44
|
+
"title": "Launch readiness poll",
|
|
45
|
+
"forma": { "version": "0.4.0" },
|
|
46
|
+
"metadata": { "program": "Example mission", "revision": 3 },
|
|
47
|
+
"nodes": [
|
|
48
|
+
{ "id": "poll", "kind": "start", "label": "Begin readiness poll" },
|
|
49
|
+
{ "id": "go", "kind": "decision", "label": "All stations go?",
|
|
50
|
+
"metadata": { "owner": "Launch director" } },
|
|
51
|
+
{ "id": "hold", "kind": "task", "label": "Call a hold",
|
|
52
|
+
"status": { "state": "pending" },
|
|
53
|
+
"color": { "fill": "#fff1d6", "foreground": "#1d1d1b" } },
|
|
54
|
+
{ "id": "launch", "kind": "end", "label": "Proceed to launch",
|
|
55
|
+
"references": [ { "id": "count", "system": "gov.nasa.example.range", "type": "Countdown", "key": "CD-77" } ],
|
|
56
|
+
"extensions": { "com.example.console": { "panel": 4 } } }
|
|
57
|
+
],
|
|
58
|
+
"edges": [
|
|
59
|
+
{ "id": "e1", "source": { "node": "poll" }, "target": { "node": "go" } },
|
|
60
|
+
{ "id": "e2", "source": { "node": "go" }, "target": { "node": "launch" }, "kind": "conditional", "label": "Go" },
|
|
61
|
+
{ "id": "e3", "source": { "node": "go" }, "target": { "node": "hold" }, "kind": "conditional", "label": "No go", "line": "dashed",
|
|
62
|
+
"metadata": { "logged": true } }
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
No coordinates appear anywhere. Forma places the nodes with its deterministic
|
|
68
|
+
default layout.
|
|
69
|
+
|
|
70
|
+
## Top-level properties
|
|
71
|
+
|
|
72
|
+
| Property | Required | Meaning |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| `format`, `formatVersion` | yes | Identify the format and its version. |
|
|
75
|
+
| `id` | yes | Stable workflow id: lowercase letters, digits and hyphens. It is also the file stem. |
|
|
76
|
+
| `title` | yes | Human name. It becomes the accessible name of the rendered diagram. |
|
|
77
|
+
| `description`, `language`, `direction` | no | Description, BCP 47 language and base text direction (`ltr`, `rtl`, `auto`). |
|
|
78
|
+
| `forma` | yes | `{ "version": "0.4.0", "requires": ["workflow.ports"] }`: the target Forma release and, optionally, the capabilities it requires. |
|
|
79
|
+
| `metadata` | no | Arbitrary JSON object. It is preserved and never interpreted. |
|
|
80
|
+
| `palette` | no | Named colour slots, `{ "finance": "token:accent-primary" }`. |
|
|
81
|
+
| `nodes` | yes | Steps or items. |
|
|
82
|
+
| `edges` | no | Connections. |
|
|
83
|
+
| `groups` | no | Groups, containers, swimlanes and phases. |
|
|
84
|
+
| `layout` | no | Workflow layout settings: `direction` (`right` or `down`), `mode` (`authored` or `auto`) and `canvas`. |
|
|
85
|
+
| `presentation` | no | `density` and a `legend` that explains colour and line meaning in text. |
|
|
86
|
+
| `extensions` | no | Producer data by namespace. |
|
|
87
|
+
| `provenance` | no | `producer`, `createdAt`, `modifiedAt`, `source` and `modifiedBy`. |
|
|
88
|
+
|
|
89
|
+
## Identity
|
|
90
|
+
|
|
91
|
+
Every addressable object has a stable `id`. Nodes, edges and groups share one id
|
|
92
|
+
namespace per workflow. Ports are unique within their node. References are
|
|
93
|
+
unique within their owner. Legend entries are unique within the legend.
|
|
94
|
+
|
|
95
|
+
Connections and memberships always point at ids. They never use array
|
|
96
|
+
positions, DOM identity or editor-only ids. Ids survive editing, re-layout and
|
|
97
|
+
saving.
|
|
98
|
+
|
|
99
|
+
## Nodes
|
|
100
|
+
|
|
101
|
+
| Property | Meaning |
|
|
102
|
+
|---|---|
|
|
103
|
+
| `kind` | `start`, `end`, `task`, `decision`, `merge`, `event`, `subprocess`, `data`, `note` or `external`. A producer kind is `<namespace>:<name>`, such as `com.example.erp:approval`, and must carry `kindLabel`. |
|
|
104
|
+
| `kindLabel` | Display text for the kind. Optional for core kinds. |
|
|
105
|
+
| `label`, `description` | Visible text. |
|
|
106
|
+
| `variant` | Forma shape: `rectangle`, `rounded`, `pill`, `ellipse` or `diamond`. Presentation only; a shape never implies a kind. |
|
|
107
|
+
| `color` | `fill`, `stroke`, `accent` and `foreground`. Each is a `#rrggbb` literal, a Forma token (`token:accent-primary`) or a palette slot (`palette:finance`). |
|
|
108
|
+
| `status` | `{ "state": "active", "label": "In progress", "detail": "...", "updatedAt": "..." }`. Core states are `none`, `pending`, `ready`, `active`, `waiting`, `complete`, `blocked`, `failed`, `skipped`, `cancelled` and `unknown`. A namespaced state needs a `label`. |
|
|
109
|
+
| `ports` | Connection points: `id`, `side` (`top`, `right`, `bottom`, `left`), `direction` (`in`, `out`, `inout`), `label`, `maxConnections`. |
|
|
110
|
+
| `references` | Domain references: `id`, `system` (a namespace), `type`, `key`, `label` and `href`. Forma displays them and never dereferences them. |
|
|
111
|
+
| `metadata` | Arbitrary JSON object. It is preserved unchanged. |
|
|
112
|
+
| `accessibility` | `name` and `description` for assistive technology, when they should differ from the visible text. |
|
|
113
|
+
| `interaction` | A declarative intent (see below). |
|
|
114
|
+
| `layout` | Optional geometry and layout intent: `x`/`y` (together or not at all), `width`, `height`, `rank`, `order` and `size` (`compact`, `standard`, `wide`). |
|
|
115
|
+
| `extensions` | Producer data by namespace. |
|
|
116
|
+
|
|
117
|
+
## Edges
|
|
118
|
+
|
|
119
|
+
`id`, `source` and `target` are required. `source` and `target` are
|
|
120
|
+
`{ "node": "...", "port": "..." }`. Edges also accept these properties:
|
|
121
|
+
|
|
122
|
+
- `direction`: `forward` (the default), `backward`, `both` or `none`.
|
|
123
|
+
- `kind`: `sequence` (the default), `conditional`, `default`, `exception`, `message`, `association`, `data` or a namespaced kind with `kindLabel`.
|
|
124
|
+
- `label`, `description`, `line` (`solid`, `dashed`, `dotted`), `width` (1 to 8) and `color.stroke`.
|
|
125
|
+
- `status`, `references`, `metadata`, `accessibility`, `interaction`.
|
|
126
|
+
- `layout`: `routing` (`straight` or `orthogonal`), `waypoints` and the label position.
|
|
127
|
+
- `extensions`.
|
|
128
|
+
|
|
129
|
+
## Groups, containers, swimlanes and phases
|
|
130
|
+
|
|
131
|
+
`kind` is one of `group`, `container`, `swimlane` or `phase`. A group lists its
|
|
132
|
+
`members` (node ids) explicitly. Nesting uses `parent`. Canvas overlap never
|
|
133
|
+
implies membership. A node belongs to at most one swimlane. A phase may cut
|
|
134
|
+
across swimlanes.
|
|
135
|
+
|
|
136
|
+
## Interactions are intents, not code
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{ "action": "command", "command": "com.example.lab:open-review", "label": "Open review",
|
|
140
|
+
"parameters": { "queue": "peer" } }
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`action` is one of the following:
|
|
144
|
+
|
|
145
|
+
- `none`, `select`, `inspect`.
|
|
146
|
+
- `navigate` or `open`, each with a `target` naming exactly one destination: `workflow`, `node`, `edge`, `group`, `reference` or `url`.
|
|
147
|
+
- `command`, which needs a namespaced `command` and a `label`.
|
|
148
|
+
|
|
149
|
+
A renderer or editor reports intents to the host application. The host decides
|
|
150
|
+
what they do. Nothing in a workflow file ever executes.
|
|
151
|
+
|
|
152
|
+
## Semantics versus layout
|
|
153
|
+
|
|
154
|
+
Everything about meaning is outside `layout`. Remove every `layout` object and
|
|
155
|
+
the workflow means the same thing. Layout operations only write `layout` fields.
|
|
156
|
+
|
|
157
|
+
- **Default layout** (`Layout.resolve`) is deterministic. Ranks come from the
|
|
158
|
+
graph (longest path, with cycles broken in document order). Swimlanes, groups
|
|
159
|
+
and containers become nested bands, so members stay inside their boundary.
|
|
160
|
+
Authored positions are kept.
|
|
161
|
+
- **Explicit layout** (`Layout.apply FillMissing` or `Layout.apply Recompute`,
|
|
162
|
+
or `forma-workflow layout [--recompute]`) writes positions into `layout`
|
|
163
|
+
fields. Tests prove it never changes ids, semantics, metadata, references or
|
|
164
|
+
extensions.
|
|
165
|
+
|
|
166
|
+
## Extensions and metadata
|
|
167
|
+
|
|
168
|
+
- `metadata` is the application's own descriptive data. It can be any JSON
|
|
169
|
+
object. Numbers keep their spelling (`1.50` stays `1.50`), and key order is
|
|
170
|
+
kept.
|
|
171
|
+
- `extensions` are keyed by a reverse-DNS namespace (`com.example.erp`). Each
|
|
172
|
+
value is a JSON object. The `forma` and `forma.*` namespaces are reserved.
|
|
173
|
+
Unknown extensions survive load, edit and save unchanged.
|
|
174
|
+
- Interoperable meaning (kinds, status, colour, references, membership) is
|
|
175
|
+
always in core fields. Never put it only in an extension.
|
|
176
|
+
|
|
177
|
+
## Canonical serialization
|
|
178
|
+
|
|
179
|
+
`Codec.serialize` and `forma-workflow normalize` write a fixed property order,
|
|
180
|
+
two-space indentation and a final newline. Empty optional arrays are omitted.
|
|
181
|
+
Canonical output is a fixed point: serializing a loaded canonical file gives the
|
|
182
|
+
same bytes.
|
|
183
|
+
|
|
184
|
+
## Versioning
|
|
185
|
+
|
|
186
|
+
`formatVersion` follows semantic versioning. This library reads `1.0.0`.
|
|
187
|
+
|
|
188
|
+
- A pre-release of the current version (`1.0.0-rc.1`) migrates automatically. It is reported as *migration required, automatic*.
|
|
189
|
+
- Any other version is reported as *migration required* with the reason. The document is not decoded.
|
|
190
|
+
|
|
191
|
+
The Forma release a workflow needs is declared separately in `forma.version`
|
|
192
|
+
and `forma.requires`. It is checked against
|
|
193
|
+
[`contracts/workflow-capabilities.json`](../../contracts/workflow-capabilities.json).
|
|
194
|
+
|
|
195
|
+
See also [VALIDATION.md](VALIDATION.md), [RENDERING.md](RENDERING.md),
|
|
196
|
+
[EMBEDDING.md](EMBEDDING.md) and [EXTERNAL-PRODUCERS.md](EXTERNAL-PRODUCERS.md).
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Rendering workflows to HTML
|
|
2
|
+
|
|
3
|
+
Forma renders a workflow to standards-based HTML with its public diagram
|
|
4
|
+
contract (`.ef-diagram*`, contract 2.1.0). Static output is HTML and CSS only.
|
|
5
|
+
Forma Studio is never involved.
|
|
6
|
+
|
|
7
|
+
## Fragment
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
forma-workflow render forma/workflows/launch.forma-workflow.json --out launch.html --deps launch.deps.json
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The fragment starts with a comment that declares what it needs:
|
|
14
|
+
|
|
15
|
+
```html
|
|
16
|
+
<!-- Requires @echelon-foundry/design-system@0.4.0/tokens.css, @echelon-foundry/design-system@0.4.0/foundations.css, @echelon-foundry/design-system@0.4.0/components.css -->
|
|
17
|
+
<figure class="ef-diagram" id="wf-static-export" aria-labelledby="wf-static-export-title">
|
|
18
|
+
<figcaption id="wf-static-export-title">Static export reference</figcaption>
|
|
19
|
+
<div class="ef-diagram__viewport" tabindex="0" role="group" aria-label="Static export reference diagram, scroll to see all items">
|
|
20
|
+
<div class="ef-diagram__canvas" style="--ef-diagram-canvas-w: 792px; --ef-diagram-canvas-h: 136px;">
|
|
21
|
+
<svg class="ef-diagram__wires" viewBox="0 0 792 136" aria-hidden="true" focusable="false">…</svg>
|
|
22
|
+
<article class="ef-diagram-node" id="wf-static-export-n-a" data-ef-shape="pill" aria-labelledby="wf-static-export-n-a-label" style="…">
|
|
23
|
+
<p class="ef-diagram-node__kind">Start</p>
|
|
24
|
+
<h3 class="ef-diagram-node__label" id="wf-static-export-n-a-label">Request</h3>
|
|
25
|
+
</article>
|
|
26
|
+
…
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
<ol class="ef-diagram__relations" aria-label="Relationships">
|
|
30
|
+
<li>Request leads to Review.</li>
|
|
31
|
+
<li>Review leads to Done: “Approved”.</li>
|
|
32
|
+
</ol>
|
|
33
|
+
</figure>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The complete reference outputs are in
|
|
37
|
+
[`examples/workflows/exports/`](../../examples/workflows/exports/). Tests
|
|
38
|
+
check them byte for byte.
|
|
39
|
+
|
|
40
|
+
## Complete document
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
forma-workflow render launch.forma-workflow.json --document --forma-base /assets/forma/ --brand example-harbor --out launch.html
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The document contains a doctype, `lang` and `dir`, charset, viewport, title,
|
|
47
|
+
description and generator metadata. It links exactly the declared stylesheets,
|
|
48
|
+
applies an optional public brand (`data-ef-brand`) and theme, and puts the
|
|
49
|
+
figure in `<main>`.
|
|
50
|
+
|
|
51
|
+
## Interactive output
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
forma-workflow render launch.forma-workflow.json --document --interactive view \
|
|
55
|
+
--runtime-base /assets/forma-workflow/ --out launch.html
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
This wraps the same static figure in the public `<forma-workflow>` element. It
|
|
59
|
+
adds the workflow source as an inert `<script type="application/json">` data
|
|
60
|
+
block and one module script from `@echelon-foundry/forma-workflow`. The
|
|
61
|
+
dependency list names that runtime. Without script, the static figure still
|
|
62
|
+
renders. Nothing private to Studio is ever emitted.
|
|
63
|
+
|
|
64
|
+
## What the output guarantees
|
|
65
|
+
|
|
66
|
+
Each guarantee is tested in `tests/workflow` and `tests/browser/workflow-static.spec.mjs`.
|
|
67
|
+
|
|
68
|
+
- **Deterministic.** The same workflow and options give byte-identical HTML.
|
|
69
|
+
- **Public contracts only.** Every class is a public Forma class that exists in Forma CSS. There are no Studio or editor classes and no editor-only ids.
|
|
70
|
+
- **No script unless asked.** Static output has no `<script>`, no event-handler attributes and no `javascript:` URLs.
|
|
71
|
+
- **Safe.** Every label, description, metadata value, reference and extension value is escaped. Unsafe URLs are dropped even if a host skips validation. The JSON data block cannot close its `<script>` element.
|
|
72
|
+
- **Accessible.** The figure is named by the title. Each node is named by its label (or its accessible name). Kind and status are text. Every connector has a sentence in `ol.ef-diagram__relations`, so relationships never depend on lines alone. Groups and lanes list their members in `dl.ef-diagram__membership`. A legend explains colour. Reading order follows the flow, not the paint order.
|
|
73
|
+
- **Responsive.** There is no page-level overflow at 320px. The canvas scrolls inside its focusable, named viewport. The relationship and membership lists reflow.
|
|
74
|
+
- **Forced colours, print and grayscale.** These follow the diagram contract fallbacks.
|
|
75
|
+
|
|
76
|
+
## Metadata in output
|
|
77
|
+
|
|
78
|
+
Metadata stays in the workflow file. It is not rendered unless the host names
|
|
79
|
+
fields to show (`--metadata owner,requirements` or
|
|
80
|
+
`RenderOptions.VisibleMetadata`). This follows FMD-META-006/007 and the
|
|
81
|
+
disclosure rules.
|
|
82
|
+
|
|
83
|
+
## .NET
|
|
84
|
+
|
|
85
|
+
```fsharp
|
|
86
|
+
let w = (Validation.load text).Workflow.Value
|
|
87
|
+
let fragment = WorkflowDocument.fragment DocumentOptions.defaults w
|
|
88
|
+
let page = WorkflowDocument.document { DocumentOptions.defaults with FormaBase = "/assets/forma/"; Brand = Some "echelon" } w
|
|
89
|
+
// fragment.Html, page.Html, page.Dependencies
|
|
90
|
+
```
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Requirements-to-evidence audit: portable workflows, embedding and HTML export
|
|
2
|
+
|
|
3
|
+
Date: 2026-10-01. Scope: kemiller2002/forma PR #95 (FORMA-GH-93, FORMA-GH-94)
|
|
4
|
+
and kemiller2002/forma-studio PR #17 (STUDIO-GH-14, STUDIO-GH-15).
|
|
5
|
+
|
|
6
|
+
**Paths.** "Forma" paths are in kemiller2002/forma; "Studio" paths are in
|
|
7
|
+
kemiller2002/forma-studio.
|
|
8
|
+
|
|
9
|
+
**Test counts** are local results from Chromium 1194. Forma has 58 F# workflow
|
|
10
|
+
tests and 25 workflow browser tests. Studio has 68 engine tests and 46 browser
|
|
11
|
+
tests. CI also runs Firefox and WebKit for Forma.
|
|
12
|
+
|
|
13
|
+
| # | Requirement | Implementation | Evidence |
|
|
14
|
+
|---|---|---|---|
|
|
15
|
+
| 3 | Forma owns the format: `.forma-workflow.json`, `forma/workflows/<id>`, versioned schema at a stable package path; all listed top-level, node and edge fields | Forma: `schemas/workflow/1.0/forma-workflow.schema.json`; package export `./workflow/forma-workflow.schema.json`; `src/workflow/Forma.Workflow/Model.fs` | Forma: `CoreTests` (fixture classes, round trip, paths); `tests/workflow-package.test.mjs` |
|
|
16
|
+
| 3 | Unknown metadata and namespaced extensions round-trip | `Codec` keeps metadata as ordered JSON with number spelling preserved | Forma: `CoreTests` "Unknown metadata …", "Namespaced extensions …"; `EditorTests`. Studio: `WorkflowStudioTests` |
|
|
17
|
+
| 4 | Semantics separate from layout; valid without coordinates; deterministic default layout; layout never changes semantics | Forma: `Layout.resolve`, `Layout.apply`, `Layout.strip` | Forma: `CoreTests` layout invariance, determinism, no overlap, lanes contain members |
|
|
18
|
+
| 5 | Validation independent of Studio; six classes; schema, ids, references, ports, groups, extensions, compatibility, static accessibility | Forma: `Validation.fs`, `JsonSchema.fs` (runs the published schema), `forma-workflow validate` | Forma: `CoreTests`, `AdversarialTests`, `tests/workflow-external.test.mjs`; `docs/workflow/VALIDATION.md` |
|
|
19
|
+
| 6 | Reusable renderer and editor in Forma: view, inspect, edit, pick and runtime; small versioned host boundary | Forma: `Editor.fs`, `Embed.fs`, `EmbedView.fs`, `EmbedProtocol.fs` (`forma-workflow-host/1`); `packages/workflow` | Forma: `EditorTests`; `tests/browser/workflow-embed.spec.mjs` |
|
|
20
|
+
| 7 | Embedding without an iframe; optional iframe with a versioned message contract; styling, brand, accessibility, keyboard, responsive, touch and reduced motion preserved; example outside Studio | Forma: `<forma-workflow>`, `frame.html`/`frame.js`, `examples/workflow-embedding` | Forma: `workflow-embed.spec.mjs` (no iframe, isolated mode, keyboard, pointer, touch at 390px, axe, forced colours and reduced motion, runtime, pick, intents) |
|
|
21
|
+
| 8 | Studio workflow editor uses the public component (dogfooding); selection, multi-selection, drag, move, resize, nodes, connections, ports, groups, lanes, metadata, colour, states, references, interactions, zoom, pan, keyboard, touch, validation, responsive | Studio: `src/kernel/workflows.js`, `StudioWorkflowInterop`, `WorkflowLibrary`; the component's capabilities | Studio: `tests/browser/workflow.spec.mjs`. Forma: `workflow-embed.spec.mjs` |
|
|
22
|
+
| 9 | Metadata is first-class, editable, and preserved when unknown | Inspector metadata editor (typed); extensions displayed and preserved | Forma: `EditorTests` metadata test, embed browser metadata test. Studio: lifecycle test |
|
|
23
|
+
| 10 | HTML fragment and complete document for a page, composition, component hierarchy or workflow; no Studio runtime, plugin or format | Studio: `HtmlExport.page`, `componentTree`, `workflow`; export panel; CLI `html`/`workflow-html` | Studio: `WorkflowStudioTests` export tests; `html-consumer.spec.mjs` |
|
|
24
|
+
| 11 | Static stays static; runtime only from the public contract, declared | Static by default; opt-in interactive output declares `@echelon-foundry/forma-workflow` | Studio: interactive export tests. Forma: `RenderTests` "Static output has no script", interactive golden |
|
|
25
|
+
| 12 | Maintainable, deterministic, public-contract HTML | Forma `Markup` tree; component-to-pattern map | Forma: `RenderTests` (public classes, determinism, goldens). Studio: `npm run html:check` |
|
|
26
|
+
| 13 | Workflow fragment and document, static and interactive, with an accessible relationship representation | Forma: `Render.figure`, `WorkflowDocument`; `ol.ef-diagram__relations`, `dl.ef-diagram__membership` | Forma: `RenderTests`, `workflow-static.spec.mjs` (axe, 320px, forced colours, brand) |
|
|
27
|
+
| 14 | Security: data only; escaping; no execution; adversarial tests | Typed intents; escaping Markup; URL policy; JSON depth and size limits; duplicate-key rejection | Forma: `AdversarialTests`, `RenderTests` escaping and data-block tests, `EditorTests` escaping. Studio: export escaping test |
|
|
28
|
+
| 15 | 17 reference fixtures as executable compatibility fixtures | Forma: `examples/workflows/forma/workflows` (19) and `fixtures.json` | Forma: `CoreTests` (every fixture validated with its expected class; required list) |
|
|
29
|
+
| 16 | External producer → validation → Studio open, edit, save → external consumer | Forma: `examples/external-producer`. Studio: the round-trip test | Studio: `docs/evidence/external-workflow-roundtrip.md` (39 preserved, 0 lost) |
|
|
30
|
+
| 17 | Studio design → HTML → ordinary web project → render, for application, responsive, workflow and branded compositions, with accessibility verified | Studio: `examples/html-consumer` | Studio: `html-consumer.spec.mjs`; `docs/evidence/html-consumer.md` |
|
|
31
|
+
| 18 | Forma gaps fixed in Forma, not Studio | Diagram contract 2.1.0 (ports, status, description, membership); `.ef-actions`; description contrast on surfaces | Forma: `tests/diagram-contract.test.mjs`, catalog check, commits on PR #95 |
|
|
32
|
+
| 19 | F#, explicit state, minimal JavaScript, Limen where appropriate | All decisions in F#. The element and adapters are plumbing. Limen is page-scoped, so it is not used for the embeddable island (ADR-0006) | `tests/workflow-package.test.mjs` (no decision code in the element); Studio: `limen verify --strict` |
|
|
33
|
+
| 20 | Unit, schema, integration, accessibility, responsive, serialization, round-trip, rendering, embedding and export tests | As listed above | As listed above |
|
|
34
|
+
| 21 | Documentation | Forma: `docs/workflow/*` (format, validation, rendering, embedding, producers), README, agent usage, diagram catalog example. Studio: `docs/WORKFLOWS.md`, `docs/HTML-EXPORT.md`, README | |
|
|
35
|
+
| 22 | Package surface: small, and usable without Studio | Forma core exports the schema, contract and examples; `@echelon-foundry/forma-workflow` exports the element, CSS, frame, schema and engine | Forma: `tests/workflow-package.test.mjs`, `tests/zero-runtime.test.mjs` |
|
|
36
|
+
| 23 | CI and publishing | Forma: `workflow-validation.yml`; browser jobs build the package; the publish job publishes the workflow package. Studio: vendor check, export determinism, browser suite | CI on PR #95 and PR #17 |
|
|
37
|
+
| 24 | Durable checkpoints | ROS work items FORMA-GH-93/94 and STUDIO-GH-14/15; HANDOFF and CURRENT-STATE entries | `./ros validate` (PR mode) passes in both repositories |
|
|
38
|
+
|
|
39
|
+
## Open items (not hidden)
|
|
40
|
+
|
|
41
|
+
1. **Flow convergence (forma-studio#16).** Studio's existing Flow surface, with
|
|
42
|
+
its profiles, named styles, mappings and merge, is not yet migrated onto the
|
|
43
|
+
portable format. Portable workflow authoring in Studio already uses the
|
|
44
|
+
public component. The broader Flow requirements in Studio's
|
|
45
|
+
`requirements/16` remain the Flow surface's backlog.
|
|
46
|
+
2. **Release pinning.** Studio vendors Forma from PR #95 with a hash manifest.
|
|
47
|
+
The pinned npm artifacts become available when PR #95 merges and the
|
|
48
|
+
publish workflow releases Forma 0.4.0 and `@echelon-foundry/forma-workflow`
|
|
49
|
+
1.0.0.
|
|
50
|
+
3. **Cross-engine browser runs.** Firefox and WebKit run in Forma CI. They were
|
|
51
|
+
not available in the implementing environment. Studio CI is Chromium only,
|
|
52
|
+
which matches its existing configuration.
|
|
53
|
+
4. **Site pages.** The Forma site shows generated workflow markup in the
|
|
54
|
+
diagram catalog. The format, embedding and validation guides are repository
|
|
55
|
+
documentation (`docs/workflow/`), not separate generated site pages.
|