qgraphflow 0.0.6
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/.agents/plugins/marketplace.json +20 -0
- package/.claude-plugin/marketplace.json +17 -0
- package/.claude-plugin/plugin.json +13 -0
- package/.codex-plugin/plugin.json +26 -0
- package/.cursor-plugin/plugin.json +9 -0
- package/.qoder-plugin/plugin.json +9 -0
- package/LICENSE +21 -0
- package/README.md +262 -0
- package/THIRD_PARTY_NOTICES.md +190 -0
- package/bin/qgraphflow.mjs +17 -0
- package/docs/clients.de.md +83 -0
- package/docs/clients.es.md +83 -0
- package/docs/clients.ja.md +83 -0
- package/docs/clients.md +83 -0
- package/docs/clients.pt.md +83 -0
- package/docs/clients.ru.md +83 -0
- package/docs/clients.zh-CN.md +83 -0
- package/docs/readme/README.de.md +262 -0
- package/docs/readme/README.es.md +262 -0
- package/docs/readme/README.ja.md +262 -0
- package/docs/readme/README.pt.md +262 -0
- package/docs/readme/README.ru.md +262 -0
- package/docs/readme/README.zh-CN.md +264 -0
- package/examples/order-flow.graph.json +94 -0
- package/package.json +61 -0
- package/skills/q-flow/SKILL.md +69 -0
- package/skills/q-flow/agents/openai.yaml +5 -0
- package/skills/q-flow/assets/layout-dist/ELK-LICENSE.md +264 -0
- package/skills/q-flow/assets/layout-dist/worker.mjs +24 -0
- package/skills/q-flow/assets/viewer/package.json +22 -0
- package/skills/q-flow/assets/viewer/src/diagrams/architecture.js +43 -0
- package/skills/q-flow/assets/viewer/src/diagrams/card.js +21 -0
- package/skills/q-flow/assets/viewer/src/diagrams/class.js +52 -0
- package/skills/q-flow/assets/viewer/src/diagrams/dataflow.js +19 -0
- package/skills/q-flow/assets/viewer/src/diagrams/deployment.js +41 -0
- package/skills/q-flow/assets/viewer/src/diagrams/drawing.js +174 -0
- package/skills/q-flow/assets/viewer/src/diagrams/er.js +34 -0
- package/skills/q-flow/assets/viewer/src/diagrams/flowchart.js +37 -0
- package/skills/q-flow/assets/viewer/src/diagrams/registry.js +28 -0
- package/skills/q-flow/assets/viewer/src/diagrams/sequence.js +38 -0
- package/skills/q-flow/assets/viewer/src/diagrams/state.js +91 -0
- package/skills/q-flow/assets/viewer/src/diagrams/usecase.js +28 -0
- package/skills/q-flow/assets/viewer/src/edge-routing.js +596 -0
- package/skills/q-flow/assets/viewer/src/export-svg.js +90 -0
- package/skills/q-flow/assets/viewer/src/graph-validation.js +286 -0
- package/skills/q-flow/assets/viewer/src/i18n-messages.json +1314 -0
- package/skills/q-flow/assets/viewer/src/i18n.js +14 -0
- package/skills/q-flow/assets/viewer/src/layout-measure.js +55 -0
- package/skills/q-flow/assets/viewer/src/layout-quality.js +164 -0
- package/skills/q-flow/assets/viewer/src/layout-spacing.js +12 -0
- package/skills/q-flow/assets/viewer/src/node-svg.js +28 -0
- package/skills/q-flow/assets/viewer/src/radix-colors.js +47 -0
- package/skills/q-flow/assets/viewer/src/sequence-executions.js +140 -0
- package/skills/q-flow/assets/viewer/src/sequence-fragments.js +208 -0
- package/skills/q-flow/assets/viewer/src/session-graph.js +43 -0
- package/skills/q-flow/assets/viewer/src/text-layout.js +126 -0
- package/skills/q-flow/assets/viewer/src/visual-style.js +158 -0
- package/skills/q-flow/assets/viewer-dist/index.html +291 -0
- package/skills/q-flow/references/acceptance.md +11 -0
- package/skills/q-flow/references/evidence-sources.md +38 -0
- package/skills/q-flow/references/graph-common.md +54 -0
- package/skills/q-flow/references/graph-schema.md +214 -0
- package/skills/q-flow/references/guided-intake.md +100 -0
- package/skills/q-flow/references/types/architecture.md +41 -0
- package/skills/q-flow/references/types/class.md +40 -0
- package/skills/q-flow/references/types/dataflow.md +41 -0
- package/skills/q-flow/references/types/deployment.md +37 -0
- package/skills/q-flow/references/types/er.md +36 -0
- package/skills/q-flow/references/types/flowchart.md +47 -0
- package/skills/q-flow/references/types/sequence.md +74 -0
- package/skills/q-flow/references/types/state.md +44 -0
- package/skills/q-flow/references/types/usecase.md +39 -0
- package/skills/q-flow/references/viewer-development.md +258 -0
- package/skills/q-flow/references/visual-contract.md +54 -0
- package/skills/q-flow/scripts/compile-layout.mjs +565 -0
- package/skills/q-flow/scripts/compile-sequence.mjs +112 -0
- package/skills/q-flow/scripts/generate-viewer.mjs +126 -0
- package/skills/q-flow/scripts/validate-graph.mjs +278 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# deployment
|
|
2
|
+
|
|
3
|
+
Where runtime units run and how they connect: hosts, networks, containers, databases, external services. Read with `graph-common.md`.
|
|
4
|
+
|
|
5
|
+
| Node `kind` | Group `kind` | Edge `kind` |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| `device`, `node`, `container`, `artifact`, `service`, `database`, `external` | `host`, `network`, `cluster`, `namespace` | `deploy`, `network`, `depends` |
|
|
8
|
+
|
|
9
|
+
## Rules
|
|
10
|
+
|
|
11
|
+
- `container` for a running container / process, `node` for a VM or machine, `device` for hardware or a client device, `artifact` for a deployable file (jar, image, bundle), `service` for a managed / logical service, `database` for stores, `external` for third-party endpoints (no `source`).
|
|
12
|
+
- Groups model real boundaries from the manifests: `network` (compose networks, VPC subnets), `host`, `cluster`, `namespace`; nest with `parentId`. A node belongs to one group; when a unit sits on two networks, place it by its primary role and say so in `facts`.
|
|
13
|
+
- Edges: `network` for traffic (label with port or protocol), `depends` for `depends_on` / environment-variable references, `deploy` from a unit to the artifact it runs. Direction follows the connection initiator.
|
|
14
|
+
- Anchor each unit to its service block in the manifest (`source.kind: "config"`); evidence for edges is `config` or `document`.
|
|
15
|
+
|
|
16
|
+
## Minimal valid skeleton
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"meta": { "title": "mini-shop deployment", "sourceRef": "deploy/docker-compose.yml", "diagramType": "deployment", "locale": "zh-CN" },
|
|
21
|
+
"groups": [{ "id": "app", "label": "app network", "kind": "network" }, { "id": "data", "label": "data network", "kind": "network" }],
|
|
22
|
+
"nodes": [
|
|
23
|
+
{ "id": "browser", "label": "browser", "kind": "external" },
|
|
24
|
+
{ "id": "api", "label": "api", "kind": "container", "groupId": "app", "subtitle": "mini-shop:0.1", "source": { "kind": "config", "file": "deploy/docker-compose.yml", "lineStart": 9, "lineEnd": 17 } },
|
|
25
|
+
{ "id": "postgres", "label": "postgres", "kind": "database", "groupId": "data", "source": { "kind": "config", "file": "deploy/docker-compose.yml", "lineStart": 30, "lineEnd": 33 } }
|
|
26
|
+
],
|
|
27
|
+
"edges": [
|
|
28
|
+
{ "id": "d1", "source": "browser", "target": "api", "kind": "network", "label": "443", "evidence": "config" },
|
|
29
|
+
{ "id": "d2", "source": "api", "target": "postgres", "kind": "depends", "label": "DATABASE_URL", "evidence": "config" }
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Frequent validation errors
|
|
35
|
+
|
|
36
|
+
- `group X.parentId does not name a group` — nested networks must list their parent first.
|
|
37
|
+
- `node X.kind is unsupported for deployment` — `service` here is a deployment unit; application-code kinds (`component`, `business`) belong to architecture.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# er
|
|
2
|
+
|
|
3
|
+
Tables (entities), their columns and keys, and the relationships with cardinalities. Read with `graph-common.md`.
|
|
4
|
+
|
|
5
|
+
| Node `kind` | Group `kind` | Edge `kind` |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| `entity` | none | `relationship` |
|
|
8
|
+
|
|
9
|
+
## Rules
|
|
10
|
+
|
|
11
|
+
- Every entity has a non-empty `fields` array; each field `{ "name", "type", "key"?: "PK" | "FK" | "UK", "nullable"?: boolean }` copied from the DDL / mapping (types verbatim, `nullable: true` only when the column allows NULL). List every column of the table you show; do not invent columns, do not infer foreign keys that the schema does not declare.
|
|
12
|
+
- A relationship needs both `sourceCardinality` and `targetCardinality`, each one of `1`, `0..1`, `*`, `1..*`, `0..*`. Source is the referenced (parent) side, target the referencing side: `customers (1) → orders (0..*)`. A UNIQUE foreign key gives `1 → 0..1`. Label with the verb in the locale's language (`places`, `contains`).
|
|
13
|
+
- Anchor each entity to its `CREATE TABLE` (or ORM class) with `source.kind: "schema"`. Keep 4–8 entities per view; large schemas become several views by aggregate.
|
|
14
|
+
|
|
15
|
+
## Minimal valid skeleton
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"meta": { "title": "Order schema", "sourceRef": "repo@main", "diagramType": "er", "locale": "zh-CN" },
|
|
20
|
+
"nodes": [
|
|
21
|
+
{ "id": "customers", "label": "customers", "kind": "entity", "source": { "kind": "schema", "file": "db/schema.sql", "lineStart": 2, "lineEnd": 6 },
|
|
22
|
+
"fields": [{ "name": "id", "type": "BIGSERIAL", "key": "PK", "nullable": false }, { "name": "email", "type": "VARCHAR(255)", "key": "UK", "nullable": false }] },
|
|
23
|
+
{ "id": "orders", "label": "orders", "kind": "entity", "source": { "kind": "schema", "file": "db/schema.sql", "lineStart": 8, "lineEnd": 15 },
|
|
24
|
+
"fields": [{ "name": "id", "type": "VARCHAR(32)", "key": "PK", "nullable": false }, { "name": "customer_id", "type": "BIGINT", "key": "FK", "nullable": false }, { "name": "status", "type": "VARCHAR(16)", "nullable": false }] }
|
|
25
|
+
],
|
|
26
|
+
"edges": [
|
|
27
|
+
{ "id": "r1", "source": "customers", "target": "orders", "kind": "relationship", "label": "places", "sourceCardinality": "1", "targetCardinality": "0..*", "evidence": "schema" }
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Frequent validation errors
|
|
33
|
+
|
|
34
|
+
- `node X.fields must be an array` / entity without fields — every entity lists at least one field.
|
|
35
|
+
- `edge r.sourceCardinality is unsupported` — only `1 0..1 * 1..* 0..*`; both ends are required.
|
|
36
|
+
- `fields[i].key is unsupported` — only `PK`, `FK`, `UK`; omit the key for plain columns. `nullable` must be boolean.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# flowchart
|
|
2
|
+
|
|
3
|
+
The steps and decisions of one function, use case or job, top to bottom. Read with `graph-common.md`.
|
|
4
|
+
|
|
5
|
+
| Node `kind` | Group `kind` | Edge `kind` |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| `start`, `end`, `process`, `decision`, `input`, `output`, `subprocess` | none | `flow`, `yes`, `no`, `success`, `failure` |
|
|
8
|
+
|
|
9
|
+
## Rules
|
|
10
|
+
|
|
11
|
+
- Exactly one `start`; one or more `end` nodes (success and failure endings may be separate nodes). Every other node lies on a path from `start` to an `end`.
|
|
12
|
+
- A `decision` asks one question in its label (`payment.ok?`) and has at least two outgoing edges: `yes` / `no` (label them with the locale's words for yes / no, or with the actual condition). Other kinds use `flow`; use `success` / `failure` for outcome edges into an `end`.
|
|
13
|
+
- `process` is a step that changes state; `input` / `output` read or emit data; `subprocess` is a call into another flow you are not expanding.
|
|
14
|
+
- The main path must read downward. When the happy path is not obvious from the edges, list it in `layout.primaryPath` (node ids in order, each pair joined by a directed edge). Feedback / retry edges are allowed and route around the outside.
|
|
15
|
+
- `module` per step is the subsystem whose work the step performs: a call into inventory is inventory's work, a payment check is payment's, a pure control decision keeps the owning component, `start` / `end` take the caller. A flow that really lives inside one subsystem keeps one module; `module.single-tone` only asks you to check.
|
|
16
|
+
- A `process`, `input`, `output` or `subprocess` has exactly one outgoing edge; only a `decision` branches (`flowchart.process-branch`).
|
|
17
|
+
- Anchor each step to the lines that implement it. Keep 8–14 nodes; merge trivial assignments into the step that owns them.
|
|
18
|
+
|
|
19
|
+
## Minimal valid skeleton
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"meta": { "title": "createOrder flow", "sourceRef": "repo@main", "diagramType": "flowchart", "locale": "zh-CN" },
|
|
24
|
+
"layout": { "primaryPath": ["start", "reserve", "ok", "persist", "done"] },
|
|
25
|
+
"nodes": [
|
|
26
|
+
{ "id": "start", "label": "createOrder called", "kind": "start", "module": "orders" },
|
|
27
|
+
{ "id": "reserve", "label": "reserve inventory", "kind": "process", "module": "inventory", "source": { "kind": "source", "file": "src/services/order-service.js", "lineStart": 17, "lineEnd": 17 } },
|
|
28
|
+
{ "id": "ok", "label": "payment.ok?", "kind": "decision", "module": "payment", "source": { "kind": "source", "file": "src/services/order-service.js", "lineStart": 25, "lineEnd": 25 } },
|
|
29
|
+
{ "id": "persist", "label": "save order and payment", "kind": "process", "module": "orders", "source": { "kind": "source", "file": "src/services/order-service.js", "lineStart": 32, "lineEnd": 34 } },
|
|
30
|
+
{ "id": "done", "label": "return order", "kind": "end", "module": "orders" },
|
|
31
|
+
{ "id": "failed", "label": "throw 402", "kind": "end", "module": "orders" }
|
|
32
|
+
],
|
|
33
|
+
"edges": [
|
|
34
|
+
{ "id": "f1", "source": "start", "target": "reserve", "kind": "flow", "evidence": "source" },
|
|
35
|
+
{ "id": "f2", "source": "reserve", "target": "ok", "kind": "flow", "evidence": "source" },
|
|
36
|
+
{ "id": "f3", "source": "ok", "target": "persist", "kind": "yes", "label": "yes", "evidence": "source" },
|
|
37
|
+
{ "id": "f4", "source": "ok", "target": "failed", "kind": "no", "label": "no", "evidence": "source" },
|
|
38
|
+
{ "id": "f5", "source": "persist", "target": "done", "kind": "success", "evidence": "source" }
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Frequent validation errors
|
|
44
|
+
|
|
45
|
+
- `layout.primaryPath has no directed edge from A to B` — the path must follow existing edges; fix the path or add the missing edge.
|
|
46
|
+
- `layout.primaryPath conflicts with node.layout.rank` — do not combine the two hints.
|
|
47
|
+
- A flow whose main path runs sideways fails the direction gate after generation: give the happy path as `primaryPath`.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# sequence
|
|
2
|
+
|
|
3
|
+
Calls and returns along one flow, with activation bars and fragments. Read with `graph-common.md`; every sequence rule the validator checks is on this page.
|
|
4
|
+
|
|
5
|
+
| Node `kind` | Group `kind` | Edge `kind` |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| `actor`, `participant`, `external`, `service`, `database` | `alt`, `opt`, `loop`, `par` | `sync`, `async`, `return` |
|
|
8
|
+
|
|
9
|
+
## Participants (nodes)
|
|
10
|
+
|
|
11
|
+
- One node per lifeline: `actor` for the human or caller, `service` / `participant` for code you read, `external` for systems outside the repository, `database` for a store or repository. Participants never have `groupId`.
|
|
12
|
+
- Left-to-right order follows first appearance in the messages; `layout.participantOrder` (all ids, once) only reproduces an existing convention.
|
|
13
|
+
|
|
14
|
+
## Messages (edges)
|
|
15
|
+
|
|
16
|
+
- Every message has `order` (positive integer, unique, increasing in time; gaps allowed), a non-empty `label` (method or message with the important arguments), `evidence`, and `kind`: `sync` (caller waits), `async` (no wait: events, publish), `return` (reply, drawn dashed).
|
|
17
|
+
- A `return` carries `"replyTo": "<call id>"`: the call is an earlier `sync` / `async` with reversed endpoints, has at most one return, and both sit in the same fragment operand. Unpaired returns are legal but drawn without pair colour and `C1` labels.
|
|
18
|
+
- Self-messages are allowed. Alternative outcomes of one call are one return labelled `ok | declined`, not two.
|
|
19
|
+
|
|
20
|
+
## Activation bars (`executions`)
|
|
21
|
+
|
|
22
|
+
Top-level array of `{ "id", "participantId", "start": { "edgeId", "at" }, "end": { "edgeId", "at" }, "parentId"? }`. `at` is `send` (participant is the edge's source) or `receive` (its target); start precedes end in `order`. Bars come only from this array: each `sync` call answered by a `return` needs a callee bar from its `receive` to the return's `send`. Bars on one participant nest (`parentId`, child inside parent) or do not overlap; start and end lie in the same operand.
|
|
23
|
+
|
|
24
|
+
## Fragments (`groups` with `operands`)
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{ "id": "retry", "label": "reserve stock", "kind": "loop", "loop": { "min": 1, "max": 3 },
|
|
28
|
+
"operands": [{ "id": "attempt", "guard": "attempt < 3", "edgeIds": ["m4", "m5"] }] },
|
|
29
|
+
{ "id": "outcome", "label": "result", "kind": "alt", "parentId": "retry", "parentOperandId": "attempt",
|
|
30
|
+
"operands": [{ "id": "ok", "guard": "reserved", "edgeIds": ["m6"] }, { "id": "no", "guard": "else", "edgeIds": [], "body": "attempt += 1" }] }
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
- `alt`: two or more guarded operands, optional `else` last. `opt`: one guarded operand. `loop`: one guarded operand plus `loop: { "min", "max" | "*" }`. `par`: two or more operands with `label` instead of `guard` (concurrent; vertical order is not time).
|
|
34
|
+
- `edgeIds` lists the operand's messages; an operand may instead or also carry `body` text or a child fragment (`parentId` + `parentOperandId`). Operand ids are required except on `alt`; a message belongs to at most one operand; successive operands' `order` ranges must not interleave. A call and its return, and a bar's start and end, stay in one operand path.
|
|
35
|
+
- Guards are display text — copy the real condition. Only fragments the source establishes (an `if`, a retry loop, concurrent handlers).
|
|
36
|
+
|
|
37
|
+
## Minimal valid skeleton
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"meta": { "title": "POST /orders → createOrder", "sourceRef": "repo@main", "diagramType": "sequence", "locale": "zh-CN" },
|
|
42
|
+
"nodes": [
|
|
43
|
+
{ "id": "client", "label": "caller", "kind": "actor" },
|
|
44
|
+
{ "id": "service", "label": "OrderService", "kind": "service", "source": { "kind": "source", "file": "src/services/order-service.js", "lineStart": 15, "lineEnd": 37 } },
|
|
45
|
+
{ "id": "payments", "label": "PaymentProvider", "kind": "external", "source": { "kind": "source", "file": "src/domain/payment-provider.js", "lineStart": 2, "lineEnd": 6 } },
|
|
46
|
+
{ "id": "bus", "label": "EventBus", "kind": "participant", "source": { "kind": "source", "file": "src/events/bus.js", "lineStart": 13, "lineEnd": 17 } }
|
|
47
|
+
],
|
|
48
|
+
"groups": [
|
|
49
|
+
{ "id": "outcome", "label": "payment result", "kind": "alt", "operands": [
|
|
50
|
+
{ "id": "ok", "guard": "payment.ok", "edgeIds": ["m4"] },
|
|
51
|
+
{ "id": "declined", "guard": "else", "edgeIds": [], "body": "release inventory, throw 402" } ] }
|
|
52
|
+
],
|
|
53
|
+
"edges": [
|
|
54
|
+
{ "id": "m1", "source": "client", "target": "service", "kind": "sync", "label": "createOrder(items)", "order": 1, "evidence": "source" },
|
|
55
|
+
{ "id": "m2", "source": "service", "target": "payments", "kind": "sync", "label": "charge(orderId, total)", "order": 2, "evidence": "source" },
|
|
56
|
+
{ "id": "m3", "source": "payments", "target": "service", "kind": "return", "label": "{ ok, transactionId }", "order": 3, "replyTo": "m2", "evidence": "source" },
|
|
57
|
+
{ "id": "m4", "source": "service", "target": "bus", "kind": "async", "label": "publish(order.created)", "order": 4, "evidence": "source" },
|
|
58
|
+
{ "id": "m5", "source": "service", "target": "client", "kind": "return", "label": "order | 402", "order": 5, "replyTo": "m1", "evidence": "source" }
|
|
59
|
+
],
|
|
60
|
+
"executions": [
|
|
61
|
+
{ "id": "x-service", "participantId": "service", "start": { "edgeId": "m1", "at": "receive" }, "end": { "edgeId": "m5", "at": "send" } },
|
|
62
|
+
{ "id": "x-charge", "participantId": "payments", "start": { "edgeId": "m2", "at": "receive" }, "end": { "edgeId": "m3", "at": "send" } }
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Frequent validation errors
|
|
68
|
+
|
|
69
|
+
- `edge m.order must be a positive integer for sequence` / `order duplicates N` — `--fix` renumbers in array order, or edit the number.
|
|
70
|
+
- `edge r.replyTo m must reference a sync or async call from a return` / `endpoints must be reversed` / `must precede its return` — point at the right call; `--fix` fills it when exactly one candidate exists.
|
|
71
|
+
- `group g.operands order ranges must be ordered and non-interleaving` — all messages of operand 1 come before operand 2; move a message or split the fragment.
|
|
72
|
+
- `group g.operands[i].id is required` — `opt` / `loop` / `par` operands need ids (`--fix` adds `op1..`).
|
|
73
|
+
- `execution x.start endpoint m does not belong to participant p` — `send` ⇒ source, `receive` ⇒ target.
|
|
74
|
+
- `Sequence group g needs explicit operands before automatic layout` — every fragment declares `operands`.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# state
|
|
2
|
+
|
|
3
|
+
The lifecycle of one component or entity: states, the events that move between them, guards and actions. Read with `graph-common.md`.
|
|
4
|
+
|
|
5
|
+
| Node `kind` | Group `kind` | Edge `kind` |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| `initial`, `state`, `final`, `choice` | none | `transition` |
|
|
8
|
+
|
|
9
|
+
## Rules
|
|
10
|
+
|
|
11
|
+
- One `initial`; any number of `state`; `final` only when the source has an explicit terminal state or the object is destroyed (`close()`, `dispose()` ends its lifecycle) — otherwise a state with no outgoing transition is simply terminal. `choice` is a diamond for a pure branch point that is not a state.
|
|
12
|
+
- Every `transition` has a `label` naming the event (`pay`, `ship`); optional `guard` (the condition text, e.g. `payment.ok`, `within 7 days`) and `action` (`record payment id`). The Viewer displays `event [guard] / action`. Copy guards and actions from the transition table or code; do not invent them.
|
|
13
|
+
- Draw a self-transition for each evidenced event a state handles while staying in it — a heartbeat, poll, retry or timer that keeps it waiting (`heartbeat [!caughtUp] / retry in 10 ms`). They show what the state waits for; skip events it only ignores or logs.
|
|
14
|
+
- A guard is a condition the source checks, never a restatement of the source state (`state != RUNNING` on a transition out of `STARTING`). When an event leads every state to one target (shutdown, close), draw the transitions the source names per state and put "entered from any state on …" in the target's `facts`.
|
|
15
|
+
- A `state` may carry `entry`, `do` and `exit`: one plain string each for what the source runs on entering the state, while it stays active and on leaving it (`"entry": "schedule the timeout, send the registration"`). Take an action out of a transition's `action` and into `entry` when the code runs it after the state is set; leave out the keys the source does not show, never invent a `do`.
|
|
16
|
+
- Anchor each state to where it is declared (enum member, transition-table row, status constant). Tag the state the component exists to reach and stay in (`running`, `active`) with `tags: ["core"]`.
|
|
17
|
+
- State colors are derived from the lifecycle and never written: the `core` state is green, a state with no way on (or only into a `final`) is slate, a state tagged `failure` is red, and the rest run warm to cool in the order the machine reaches them (orange, blue, teal, …). So always tag the goal state `core`: without one the states keep their module color. Use `tags: ["failure"]` only for an error or aborted state, not for every cancellation or timeout.
|
|
18
|
+
- One machine per view; keep 5–10 states. States carry the component's `module`; a `choice` takes the module of the subsystem whose result it branches on; `initial` / `final` never carry a wash.
|
|
19
|
+
|
|
20
|
+
## Minimal valid skeleton
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"meta": { "title": "Order status", "sourceRef": "repo@main", "diagramType": "state", "locale": "zh-CN" },
|
|
25
|
+
"nodes": [
|
|
26
|
+
{ "id": "initial", "label": "start", "kind": "initial" },
|
|
27
|
+
{ "id": "created", "label": "created", "kind": "state", "source": { "kind": "source", "file": "src/domain/order.js", "lineStart": 22, "lineEnd": 22 } },
|
|
28
|
+
{ "id": "paid", "label": "paid", "kind": "state", "entry": "record payment id", "source": { "kind": "source", "file": "src/domain/order-state.js", "lineStart": 5, "lineEnd": 5 } },
|
|
29
|
+
{ "id": "cancelled", "label": "cancelled", "kind": "state", "source": { "kind": "source", "file": "src/domain/order-state.js", "lineStart": 6, "lineEnd": 6 } }
|
|
30
|
+
],
|
|
31
|
+
"edges": [
|
|
32
|
+
{ "id": "s0", "source": "initial", "target": "created", "kind": "transition", "label": "new Order", "evidence": "source" },
|
|
33
|
+
{ "id": "s1", "source": "created", "target": "paid", "kind": "transition", "label": "pay", "guard": "payment.ok", "evidence": "source" },
|
|
34
|
+
{ "id": "s2", "source": "created", "target": "cancelled", "kind": "transition", "label": "cancel", "action": "release inventory", "evidence": "source" }
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Frequent validation errors
|
|
40
|
+
|
|
41
|
+
- `edge s.guard must be a string` — guards and actions are plain text, not objects or booleans.
|
|
42
|
+
- `edge s.kind is unsupported for state` — every edge is a `transition`; encode success / failure in the label or guard.
|
|
43
|
+
- Layout `route.anchor … exceeds an endpoint side` on a `final` with several long-labelled incoming transitions — when the object can be recreated (a cache slot, a session) let removal return to the empty state instead of a `final`.
|
|
44
|
+
- Layout `spacing.label-edge` between two self-transitions on one state — merge their events into one label (`tick, retry`) when they share the action; otherwise keep the one that keeps the state waiting and move the other into the state's `facts`.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# usecase
|
|
2
|
+
|
|
3
|
+
Actors and what they can do, with include / extend relationships between use cases, inside a system boundary. Read with `graph-common.md`.
|
|
4
|
+
|
|
5
|
+
| Node `kind` | Group `kind` | Edge `kind` |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| `actor`, `usecase` | `system` | `association`, `include`, `extend` |
|
|
8
|
+
|
|
9
|
+
## Rules
|
|
10
|
+
|
|
11
|
+
- Use cases sit inside one `system` group (`groupId`); actors stay outside every group. Name use cases as goals (`Create order`, `Export settlement`), actors as roles (`Buyer`, `Support`), not as UI screens or classes.
|
|
12
|
+
- `association` actor → use case (who can do it). `include` base → included (always happens as part of the base); `extend` extension → base (happens only under a condition — put the condition in `label`). Both ends of `include` / `extend` must be use cases. Do not add `«include»` to labels; the Viewer draws the stereotype.
|
|
13
|
+
- Evidence is usually `document` (requirements, README) or `source` (route handlers, permission checks). Anchor use cases to the requirement line or the handler that implements them; actors have no anchor.
|
|
14
|
+
- Keep 3 actors and ≤ 10 use cases per view; more actors mean more views.
|
|
15
|
+
|
|
16
|
+
## Minimal valid skeleton
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"meta": { "title": "Shop use cases", "sourceRef": "docs/use-cases.md", "diagramType": "usecase", "locale": "zh-CN" },
|
|
21
|
+
"groups": [{ "id": "system", "label": "mini-shop", "kind": "system" }],
|
|
22
|
+
"nodes": [
|
|
23
|
+
{ "id": "buyer", "label": "Buyer", "kind": "actor" },
|
|
24
|
+
{ "id": "create", "label": "Create order", "kind": "usecase", "groupId": "system", "source": { "kind": "document", "file": "docs/use-cases.md", "lineStart": 7, "lineEnd": 7 } },
|
|
25
|
+
{ "id": "pay", "label": "Pay", "kind": "usecase", "groupId": "system", "source": { "kind": "document", "file": "docs/use-cases.md", "lineStart": 15, "lineEnd": 15 } },
|
|
26
|
+
{ "id": "cancel", "label": "Cancel order", "kind": "usecase", "groupId": "system", "source": { "kind": "document", "file": "docs/use-cases.md", "lineStart": 15, "lineEnd": 15 } }
|
|
27
|
+
],
|
|
28
|
+
"edges": [
|
|
29
|
+
{ "id": "u1", "source": "buyer", "target": "create", "kind": "association", "evidence": "document" },
|
|
30
|
+
{ "id": "u2", "source": "create", "target": "pay", "kind": "include", "evidence": "document" },
|
|
31
|
+
{ "id": "u3", "source": "cancel", "target": "pay", "kind": "extend", "label": "payment declined", "evidence": "document" }
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Frequent validation errors
|
|
37
|
+
|
|
38
|
+
- `node X.groupId places an actor inside a system boundary` — remove `groupId` from actors.
|
|
39
|
+
- `edge u include endpoints must both be use cases` — `include` / `extend` never touch actors; use `association` for actor links.
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# Viewer development and maintenance
|
|
2
|
+
|
|
3
|
+
Modules are composed at build time: after source changes and a build, the generator embeds every feature in standalone HTML. There is no runtime plugin download or hot loading. Ordinary graph generation outputs `index.html`, `graph.json` and one SVG per view, drawn by the same `export-svg.js` the Viewer's in-place save uses.
|
|
4
|
+
|
|
5
|
+
## Change entry points
|
|
6
|
+
|
|
7
|
+
Source paths below are relative to `assets/viewer/src/`.
|
|
8
|
+
|
|
9
|
+
| Goal | Entry point | Scope |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| Add a diagram type | `diagrams/<type>.js`, `diagrams/registry.js` | Names, order, legal kinds, type-specific validation, rendering, outlines and relationship policy |
|
|
12
|
+
| Change theme, font sizes or line heights | `visual-style.js`, `radix-colors.js` | Radix scales, theme variables, semantic colors, core recognition and dimensions shared by page and export |
|
|
13
|
+
| Change node shapes or internal layout | Matching `diagrams/<type>.js`; shared cards use `diagrams/card.js` | Both page and SVG/PNG nodes |
|
|
14
|
+
| Change shared SVG typography and primitives | `diagrams/drawing.js` | `svgStyles()`, escaping and shared primitives; scoped on the page and reused in export |
|
|
15
|
+
| Change toolbar, details or responsive shell | `ViewerShell.jsx`, `styles.css` | Page shell; no second HTML/CSS implementation of node content |
|
|
16
|
+
| Change restrained motion effects | the motion-effects block at the end of `styles.css` | Moving stroke widths and reduced selection halo while flowing; no permanent role/module glow or colored vignette; honors contrast/transparency preferences |
|
|
17
|
+
| Change selection or search | `features/useSelection.js`, `search.js` | Shared selection, feedback, keyboard and dismissal; search ranking is independently testable |
|
|
18
|
+
| Change directional edge motion | `features/useViewerController.js`, `features/usePresentation.js`, `DiagramCanvas.jsx`, `styles.css` | Direction-only animation controlled by its switch and reduced-motion preference; dashed sequence baselines, masks and selection strokes travel together while retaining gaps |
|
|
19
|
+
| Change the reading legend | `ViewerShell.jsx`, `styles.css`; content from `legend.js`, `visual-style.js` | Floating legend popover from actual categories and line styles; retain original symbols, shared `nodeAppearance` colors and core priority |
|
|
20
|
+
| Change panels and focus | `features/usePanels.js` | Mobile mutual exclusion, visibility and focus return; preserve panel preference on view switches |
|
|
21
|
+
| Change canvas fullscreen | `features/useFullscreen.js`, `features/useSelection.js` | Native fullscreen, failure notices and focus return; selection in fullscreen does not open an external Inspector, Escape exits fullscreen first |
|
|
22
|
+
| Change dragging, viewport, lock or spacing | `features/useGraphLayout.js`, `layout-nudge.js` | Current positions and layout operations; D3 remains a bounded nudge |
|
|
23
|
+
| Change presentation state | `features/usePresentation.js` | Project selection and search into nodes and edges without a second state owner |
|
|
24
|
+
| Change downloads | `features/download.js`, `export-svg.js` | Static SVG from current coordinates; PNG rasterized from that SVG |
|
|
25
|
+
| Change routing or layout checks | `edge-routing.js`, `text-layout.js` | Paths, label wrapping and measurement shared by page, exports and validation |
|
|
26
|
+
|
|
27
|
+
`main.jsx` owns loading, view switches, theme and per-view edit drafts. `features/useViewerController.js` combines capabilities and coordinates save/reset operations; `ViewerShell.jsx` binds the UI. Extend the relevant module first. Create a new Hook only for independent state and lifecycle.
|
|
28
|
+
|
|
29
|
+
## Add a diagram type
|
|
30
|
+
|
|
31
|
+
1. Add a module in `diagrams/` with a default-exported definition.
|
|
32
|
+
2. Import it in `diagrams/registry.js` and add it to `DIAGRAMS`. Registry order is collection menu order; standalone graphs still have no type menu.
|
|
33
|
+
3. Update `graph-schema.md`, `visual-contract.md` and the authoring instructions in SKILL so the author knows the type. Registering a type need not change the data structure.
|
|
34
|
+
4. Build the template, generate examples and verify type-specific nodes, relationships and browser interactions.
|
|
35
|
+
|
|
36
|
+
Reuse existing rules and primitives. A card view inheriting every architecture rule needs only `{ ...architecture, id: 'new-view', label: 'New view' }` and registration. An independent type commonly contains:
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
export default {
|
|
40
|
+
id: 'new-view', label: 'New view',
|
|
41
|
+
nodeKinds: ['component'], groupKinds: [], edgeKinds: ['call'],
|
|
42
|
+
outline, // (node, x, y) => [[SVG tag name, geometry attributes], ...]
|
|
43
|
+
render, // (node, x, y, fill, stroke, palette) => SVG string
|
|
44
|
+
};
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`render` paints the body with `paint(outline(node, x, y), { fill, stroke })` and text with `text` / `centeredTitle`. Module code is trusted; graph data is not. Never concatenate input into SVG tags, attributes or text without shared escaping.
|
|
48
|
+
|
|
49
|
+
Add optional hooks only when needed:
|
|
50
|
+
|
|
51
|
+
- `validateNode`, `validateEdge`: append constraints after common validation; reuse supplied `requireString`, `validateStringArray`, etc. Each validation creates a fresh sequence-order set.
|
|
52
|
+
- `edgeLabel`, `undirected`, `dashedKinds`, `markers`: relationship labels, direction, notation-specific dashes beyond evidence styles and existing UML markers.
|
|
53
|
+
- `cardLayout`, `compartments`, `sequence`, `cardinalities`, `endpointStub`, `selectionHeight`: existing card checks, core compartments, lifelines, ER endpoints and selection-height rules.
|
|
54
|
+
|
|
55
|
+
Rectangular cards can reuse current rules. Entirely different routing, connection points or UML symbols still require shared routing or drawing extensions; registration cannot infer unknown geometry. Add names and `nodeAppearance` classification in `visual-style.js` for new semantic kinds. Retain the Radix MIT notice from `radix-colors.js` in generated HTML and exported SVG.
|
|
56
|
+
|
|
57
|
+
## Shared rendering constraints
|
|
58
|
+
|
|
59
|
+
`DiagramCanvas.jsx` and `export-svg.js` both call `renderNode()` in `node-svg.js`. The page inserts SVG into React Flow nodes; export passes a canvas offset to the same renderer. `SelectionOutline.jsx` calls `renderSelection()` and reuses the type's `outline`, changing only stroke, opacity and shadow.
|
|
60
|
+
|
|
61
|
+
Separate positions, content and transient interaction: dragging changes current coordinates; selection changes neither coordinates nor shapes. Exports receive only the current graph and theme, never selection or animation state. Boundaries, React Flow marker adaptation and page controls still have host-specific rendering. Shared node content does not mean identical page DOM and export SVG.
|
|
62
|
+
|
|
63
|
+
## Build and regression
|
|
64
|
+
|
|
65
|
+
Run in the skill directory with existing dependencies and browser tooling:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npm --prefix assets/viewer run build
|
|
69
|
+
node --test scripts/*.test.mjs
|
|
70
|
+
node scripts/generate-viewer.mjs /tmp/graph.json /tmp/new-viewer
|
|
71
|
+
node scripts/browser-interactions.mjs /tmp/new-viewer /tmp/new-viewer-check
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The lower-left fullscreen button puts the canvas into native fullscreen; the floating panels and top toolbar stay outside. After the dimensions change, fit the current graph once without changing selection or layout. Keep the current viewport when exiting. The target icon fits the drawing; the four-corner icon toggles fullscreen. Regenerate previously created HTML to include new features. When fullscreen is unsupported, mark the control unavailable; announce request failures in the canvas status region.
|
|
75
|
+
|
|
76
|
+
Fullscreen acceptance uses real browser APIs. `QA_HEADED=1` opens a browser window for fullscreen, Escape and focus checks:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
QA_HEADED=1 QA_ONLY_EXTRAS=1 QA_EXTRAS=fullscreen,fullscreen-errors \
|
|
80
|
+
node scripts/browser-interactions.mjs /tmp/new-viewer /tmp/fullscreen-check
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The browser script covers every supplied type at three sizes, in light and dark themes, with interactions. Use a nine-type collection for the full matrix. `QA_FIXTURE_DIR` adds special-shape fixtures. The script needs adjacent source modules and cannot be copied as a standalone script.
|
|
84
|
+
|
|
85
|
+
Details follow the user's selection: `useSelection` owns selection and `useViewerController` derives `inspectedNode`; the quick-look card and Inspector in `ViewerShell` consume it. There is no autoplay, step-by-step reading or flow orchestration. The legend is in a floating button at the canvas's top left; `.inspector-facts` remains the last details section. `DiagramCanvas`'s `has-flow` and `styles.css` control static-layer contrast during edge motion; selection must not fill the moving dash gaps.
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
QA_ONLY_EXTRAS=1 QA_EXTRAS=flow-contrast,inspector-sync \
|
|
89
|
+
node scripts/browser-interactions.mjs /tmp/new-viewer /tmp/new-viewer-sync-check
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`diagram-modules.test.mjs` adds a tenth test type in a temporary copy, changes only that copy's module and registry, and performs validation, build and generation. The product still supports nine types. Set `MODULE_TEST_OUTPUT=/tmp/new-module-check` to retain the copy and use its `skills/q-flow/scripts/browser-interactions.mjs` to check `page/`. The copy preserves repository hierarchy and root third-party notices to verify actual build dependencies. The directory must not already exist.
|
|
93
|
+
|
|
94
|
+
Rebuild `assets/viewer-dist/index.html` after Viewer changes. After installation, check the actual cache and generate acceptance output with the installed version in a new session. Existing standalone HTML embeds old code and must be regenerated.
|
|
95
|
+
|
|
96
|
+
## QGraphFlow naming and migration
|
|
97
|
+
|
|
98
|
+
| Identifier | Old | New |
|
|
99
|
+
| --- | --- | --- |
|
|
100
|
+
| Product | CodeGraph Flow | QGraphFlow |
|
|
101
|
+
| Plugin ID | `codegraph-flow` | `qgraphflow` |
|
|
102
|
+
| Skill directory and invocation | `create-interactive-codegraph` / `$create-interactive-codegraph` | `q-flow` / `$q-flow` |
|
|
103
|
+
| Skill display name | CodeGraph Flow (with a Chinese subtitle) | Q flow |
|
|
104
|
+
| Private Viewer package | `codegraph-flow-viewer` | `qgraphflow-viewer` |
|
|
105
|
+
| Default delivery directory | `docs/codegraph-flow/<scope>-<diagram-type>/` | `docs/qgraphflow/<scope>-<diagram-type>/` |
|
|
106
|
+
|
|
107
|
+
The package keeps only the new skill entry, without old aliases. Its scope remains nine software diagram types. Renaming MapSprig / QMindFlow is outside this migration.
|
|
108
|
+
|
|
109
|
+
From the repository root, use the new paths:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
node skills/q-flow/scripts/validate-graph.mjs /tmp/graph.json
|
|
113
|
+
node skills/q-flow/scripts/generate-viewer.mjs /tmp/graph.json docs/qgraphflow/example-architecture
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The default directory is a skill delivery convention; the generator still requires an explicit output directory. User-chosen legacy directories, including `docs/codegraph-flow/`, remain usable. Replacing existing output still requires `--force`. Old `graph.json` files need no rewrite; authored legacy names in titles, sources, nodes and evidence are preserved. Old HTML remains usable offline but shows its embedded old branding; regenerate from its original JSON to update it.
|
|
117
|
+
|
|
118
|
+
`CodeGraph`, `codegraph`, `@colbymchenry/codegraph` and `.codegraph/` belong to the external analysis tool and are unchanged. Internal `__CODEGRAPH_FLOW_DATA__`, `codegraph-*` SVG identifiers and test temporary-directory prefixes are also preserved.
|
|
119
|
+
|
|
120
|
+
### Local installation and updates
|
|
121
|
+
|
|
122
|
+
Installation, update, removal and verification status for Codex, Claude Code, Qoder and Cursor live in the [client installation guide](../../../docs/clients.md). Repository edits do not refresh installed plugins automatically. Verify the actual source and version during installation or migration, use client management controls, and preserve user diagrams and other plugin configuration.
|
|
123
|
+
|
|
124
|
+
## Viewer visual and interaction contract
|
|
125
|
+
|
|
126
|
+
Read this section only for Viewer maintenance or interaction audits. Graph authoring uses [visual-contract.md](visual-contract.md).
|
|
127
|
+
|
|
128
|
+
### Shared presentation
|
|
129
|
+
|
|
130
|
+
- Use the canvas-first React Flow shell: the canvas fills the window and runs under one 52px material toolbar (navigation toggle, view menu for collections, title and subtitle, search with a results popover, a `···` menu with export / reset / layout-lock switch / spacing / appearance, and the Inspector toggle). There is no board header, footer or brand block; the product name appears only in the document title. Graph navigation and the node Inspector are floating panels that slide in from their own edge and start collapsed at every width; clicking a node shows a quick-look card beside it (type, name, responsibility, source anchor, up to four tags, `View details`) instead of opening the Inspector. Let the diagram carry the strongest visual emphasis.
|
|
131
|
+
- The card wash is on by default on every page; the toolbar switch turns it off for the current session only (module washes and the bodies of state tones go plain, frames and chips stay), and no preference is stored. Appearance follows the system `prefers-color-scheme` by default and updates live; the `···` menu offers a `System / Light / Dark` segmented control whose manual choice wins in both directions. Theme changes preserve viewport, search, selection, layout lock, and panel state.
|
|
132
|
+
- Use Radix Colors (MIT) as the shared palette: Slate for cool-neutral surfaces, Iris for core components and interactions, Cyan for data, Red for explicit failures, and eight identity scales (Blue, Orange, Teal, Crimson, Violet, Grass, Plum, Indigo) for `module` names. Every card color is a Radix step or a documented wash of one. Keep copyright and license notices in distributed source, standalone HTML, and SVG; no runtime CDN or component-library dependency is required.
|
|
133
|
+
- Emphasize an actual business center with `business` or a case-insensitive `core`/`business` tag through a soft Iris 5 ring behind its frame; the frame, chip and wash still belong to its module and the text stays ink. Explicit failures take a Red 11 frame and a Red 5 ring over any module; the chip retains identity. Initial/final symbols keep their notation. Decisions, choices, `alt` and FK references do not imply failure. In a state diagram the states wear a lifecycle tone instead of the module's frame and wash (see State notation). Do not add literal color fields.
|
|
134
|
+
- Page, node drawings, MiniMap, Inspector dots, legends, and exports reuse `visual-style.js`. The chip, frame, wash and outgoing lines say whose a node is (module); a ring says it is the business center or a failure; text never follows either. Cards sit on Slate 1, dense ER/class rows on Slate 2; the light canvas is Slate 1 (`#fcfcfd`, near-white), the same step-1 rule as dark mode. Use corresponding dark scales rather than applying light colors unchanged in dark mode.
|
|
135
|
+
- Chrome colors (toolbar, panels, Inspector, canvas controls) are expressed through tokens that `styles.css` derives from the palette variables with `color-mix`: four label levels (`--label`, `--label-2/3/4`), one separator (`--sep`), a three-step fill scale (`--fill`, `--fill-2/3`) and three materials (`--material-thick`, `--material`, `--material-thin`), so dark mode only overrides the material base and shadows. Chrome text uses the 11 / 12 / 13 / 15 / 20 px scale while node SVG typography keeps `TYPOGRAPHY` / `--font-*`; every control has a `:active` press state and the shared `:focus-visible` ring; panels separate with a .5px hairline plus one shadow layer, and `1px solid` stays on canvas glyphs only. Besides `prefers-reduced-motion`, the chrome responds to `prefers-reduced-transparency` (materials become opaque `--panel`, no blur) and `prefers-contrast: more` (separators and secondary text take the label color; floating layers get a 1px outline).
|
|
136
|
+
|
|
137
|
+
| Role | Radix scale / step |
|
|
138
|
+
| --- | --- |
|
|
139
|
+
| Page / ordinary surface | Slate 1 / Slate 2 |
|
|
140
|
+
| Main / secondary text | Slate 12 / Slate 11 |
|
|
141
|
+
| Core ring / accent | Iris 5 ring behind the frame / Iris 11 for success edges, keys and the initial symbol |
|
|
142
|
+
| Interaction / directed motion | Iris 11 / actual relationship color |
|
|
143
|
+
| Data glyph / frame without module | Cyan 11 |
|
|
144
|
+
| Failure frame / ring | Red 11 / Red 5 |
|
|
145
|
+
| Neutral frame / icon plate | Slate 9 / Slate 3 |
|
|
146
|
+
| Module chip / frame and lines / card wash / header wash | Identity step 9 / step 10 / 5% (dark 9%) of step 9 over Slate 1 / 10% (dark 16%) over Slate 2 |
|
|
147
|
+
| State goal / ended / failed (body, frame) | Grass / Slate / Red: step 3, step 11 (dark 10) |
|
|
148
|
+
| State in flight (body, frame) | Ramp Orange, Blue, Teal, Violet, Plum, Indigo: step 3, step 11 (dark 10) |
|
|
149
|
+
| Boundary surface / nested / hairline | Slate 2 / Slate 1 (dark Slate 3) / Slate 5 |
|
|
150
|
+
| Decorative separator / meaningful edge | Slate 6 / Slate 11 |
|
|
151
|
+
|
|
152
|
+
- Ordinary node bodies take a faint wash of their module chip, or the plain surface when they have no module; initial/final symbols retain their notation. `moduleColorMap()` hashes the module name into the same bounded palette slot across diagrams, additions/removals and standalone exports and returns a `{ name, fill, accent }` tone. Slots can repeat; module labels remain authoritative. The chip, the 1.5px frame, the wash and every relationship leaving the card carry identity; a failure frame takes precedence. Every identity frame stays ≥20 ΔE (CIELAB) from the Cyan and Red role strokes and ≥12 ΔE from each other. ER/class tint their headers and keep dense rows neutral. Large groups, swimlanes and sequence fragments use the neutral boundary surface with a hairline. Do not recolor same-module neighbors merely to alternate fills.
|
|
153
|
+
- `groupAppearanceMap()` gives every boundary the neutral Slate 2 surface with a Slate 5 hairline and steps directly nested boundaries onto Slate 1 (dark Slate 3); there is no colored accent. Fills are opaque to prevent nested accumulation; render parents before children and keep every group below edges/cards. `groupFrameSvg()` shares the same frame across page, minimap and SVG/PNG. Groups do not inherit module/status colors; names and sequence operators retain meaning when slots repeat. Headings and operators use neutral main ink. Verify edge contrast against group fills as well as the canvas.
|
|
154
|
+
- Build the legend from categories and line styles actually present: role swatches for the outlines in view plus one chip-colored entry per module. Use current theme tones. Test diagram text at ≥4.5:1 and meaningful outlines/lines at ≥3:1 against their actual backgrounds, including opacity. Preserve non-color symbols and text for grayscale/color-vision accessibility.
|
|
155
|
+
- Open the reading legend from the `Legend` floating button at the canvas's top-left as a popover that also holds the `Edge animation` switch when directed relationships exist. Keep each original symbol before its label, including its shape, theme colors and solid/dashed line style, as plain inline text that wraps within the popover. The button yields to the right of an open navigation panel; pan/zoom leaves its position and text size unchanged, and Escape or an outside click closes the popover.
|
|
156
|
+
- Directed relationships show one clearly visible moving dash overlay from source to target by default; undirected relationships remain static. Preserve the solid/dashed evidence baseline beneath the overlay. For sequence messages the baseline stays opaque and the 3.2-graph-unit overlay has no glow. Dashed returns / framework / inference messages move the baseline, its same-route `5 5` mask and selection strokes together: the dashes travel from source to target while gaps stay clear at each animation phase. Brightness changes within fixed dash positions are not sufficient. User selection keeps motion running. Switching sequence flow off or reducing motion removes the overlay and stops the baseline's dash phase.
|
|
157
|
+
- Hover and selection emphasis use outlines and shadows without scaling node geometry or replacing semantic fills and borders. User selection adds one shared 760ms outline/glow rebound to the node and its direct incident edges, then retains static emphasis; only stroke width, opacity, and shadow animate.
|
|
158
|
+
- The motion-effects block at the end of `styles.css` supplies clear moving stroke widths without permanent colored glows, tinted node shadows or a colored canvas vignette. All meaningful edge baselines remain opaque. Existing restrained selection feedback remains local; reduced-transparency and high-contrast preferences disable decorative halos without changing notation or baseline contrast.
|
|
159
|
+
- Honor reduced-motion preferences by disabling relationship and selection motion, and applying view changes without animation.
|
|
160
|
+
- SVG and PNG downloads use the current theme, notation, typography hierarchy, and shared orthogonal paths. They exclude temporary selection and search highlights; visible labels and symbols must remain within their shapes after export; SVG descriptions preserve the complete authored text.
|
|
161
|
+
|
|
162
|
+
### Interaction
|
|
163
|
+
|
|
164
|
+
- A standalone graph has no view menu. A requested graph collection exposes its diagram types in the toolbar view menu (`role=menu` with `menuitemradio` items) in canonical order, the current item checked and each item showing its relationship count. Switching retains each graph's saved text and positions, clears search and the previous selection, uses the default overview/local reading rule below, and applies that graph's initial core selection.
|
|
165
|
+
- There is no automatic playback, step control, reading tour, current step, or completed-step state. Ignore legacy `playback` metadata. Directional edge motion remains independent and never claims to show runtime order.
|
|
166
|
+
- Initially select the first explicitly marked core node, if any, with static emphasis on it and its direct incident edges. Do not pulse for this initialization; graph switching follows the same rule. Without a core marker, leave selection empty.
|
|
167
|
+
- Selecting a node shows its quick-look card and highlights its direct incoming/outgoing edges together; include a self-loop once and do not traverse further or dim unrelated relationships. Reuse the current routed path for edge emphasis without adding arrowheads or changing arrow sizes. Preserve evidence dashes, ER cardinality, and UML symbols.
|
|
168
|
+
- Match selection outlines to the actual shape: cards, diamonds, parallelograms, ellipses, and state circles. In sequence diagrams emphasize only participant headers or actor figures, never the full lifeline box. After the 760ms rebound, retain static emphasis; selecting another node replaces the whole highlighted set, and selecting the same node replays once.
|
|
169
|
+
- Node clicks and drag start select a node and show its quick look; directory/search and Enter/Space open its Inspector. Selection leaves directed edges flowing. Apply selection feedback and detail positioning once per operation; dragging does not recenter the viewport or move focus away from its target.
|
|
170
|
+
- Clear selection with a canvas click, detail close, or Escape. Search dims non-matching nodes without changing topology.
|
|
171
|
+
- The Inspector retains the selected node and all its facts, fields, nullability, attributes, methods, source anchors and tags until the user changes or clears selection. It never advances automatically or steals focus.
|
|
172
|
+
- Directed flow must remain visibly distinguishable while its node is selected. Static emphasis must not fill the moving dash gaps with an opaque same-color line. Validate all incident directed edges, not only the first edge or animationPlayState. Reduced motion and the independent flow switch retain priority.
|
|
173
|
+
- Search ignores case and surrounding whitespace. Rank exact names, name prefixes, name substrings, subtitle/tags, then facts/fields/attributes/methods; preserve original order within ties and take eight results after sorting.
|
|
174
|
+
- Layout is locked by default. An explicit control enables dragging; downloads use the current node positions.
|
|
175
|
+
- The `Arrange` action uses bounded D3 nudging after unlocking layout. With a selection it moves that node and its one-hop neighbors; without a selection it moves all nodes. It targets 49px rectangle clearance while staying close to authored positions, keeps contained nodes inside their smallest boundary, and only moves sequence participants horizontally. It is spacing cleanup, not a fresh topology or full automatic layout.
|
|
176
|
+
- Initial canvas budgets are 2400×1600 graph units for architecture / er / deployment / class / usecase / dataflow, 1600×2400 for flowchart / state, and content-adaptive for sequence. These are starting budgets, not minimum borders, export resolutions or aspect-ratio requirements. Preserve full text and type semantics; expand for spacing and routes, keep small graphs compact, and never shrink text or add empty padding to match a ratio. Start with 64 units between peers and 80–96 between layers; expand only the affected label/port corridor. Keep 24 around labels and below measured group headings, with 32 at group sides. Wrap complete long labels instead of spreading every node; sequence messages constrain their own participant span and measured row height. Budgets alone do not rearrange existing geometry.
|
|
177
|
+
- If a boundary cannot fit the preferred header/padding within the 156px movement limit, retain the node's authored position and report remaining layout issues. Unrelated nodes retain their exact coordinates, including fractions. Every operation refreshes the status message and its 4.5s display timer, even when the text repeats.
|
|
178
|
+
- Spacing cleanup never changes graph evidence, edge route hints, groups, selection, viewport, panels, or theme. `Reset` restores authored `graph.json` positions and the reading view, clears search, selection, and old operation messages, and restores the default edge-flow switch. Keep the current theme, panel visibility, and layout lock; announce the completed reset in the existing status region.
|
|
179
|
+
- Pan, zoom, fit view, reset, SVG download, and PNG download remain available.
|
|
180
|
+
- `Save changes` saves all views' current text and positions in the original single-graph or collection shape, preserving metadata, source anchors, and structured fields. Where the browser offers a directory picker (Chromium, including pages opened from disk), the user picks the page's own folder once and both the sibling `graph.json` and the open page are rewritten in place: the page on disk is re-read and only its embedded `graph-data` element changes, through the same `pageWithGraph()` the generator uses, so reloading shows the edits. Picking a folder that does not contain the page saves nothing and names the page in the status. Otherwise a native file picker or a `graph.json` download preserves the edits for regeneration. Cancellation or write failure preserves edits; reset affects only the current graph and is included in the next save. Browser regression must cover switching away and back, reset isolation, JSON download, and regeneration from that JSON.
|
|
181
|
+
- Keyboard selection and panel dismissal preserve graph topology; Backspace and Delete do not remove nodes from this Viewer.
|
|
182
|
+
- Desktop opening, resetting, switching views and entering fullscreen fit the whole diagram. Sequence diagrams at ≤700px instead start at zoom .75 around the first core participant (or first participant), with its head below the toolbar. Explicit `Fit canvas` always shows the whole graph at every width. Fit bounds include nodes, groups, routes, labels and ER symbols; exports always use these full bounds.
|
|
183
|
+
- Every overview fit subtracts toolbar and floating-panel chrome using `readingPadding()`; each open desktop side panel occupies 304px + 24px, closed sides reserve 24px. The bottom boundary clears measured visible controls by 12px. Padding uses `px` strings, not numeric ratios. Panels outside fullscreen and full-width mobile panels do not reduce the reading rectangle.
|
|
184
|
+
- Opening navigation or Inspector pans only as needed to reveal the selected node, or sequence participant head/message label, without changing zoom. An already visible or absent selection does not move. Mobile panels do not trigger reveal. Closing panels preserves the current viewport, including user navigation.
|
|
185
|
+
- Directory, search and participant Enter/Space locate ordinary nodes as before. Sequence locate uses `max(currentZoom, .75)` (maximum 2), anchors the visible head near the top of the final reading rectangle, and issues one viewport target. On mobile it prepares the canvas seen after closing the mutually exclusive panel. Clicking or starting a drag does not locate. `occupiedBox()` uses the shared 72px participant / 108px actor head; an actor subtitle adds 22px, without changing the authored lifeline end.
|
|
186
|
+
- All programmatic viewport moves (fit, locate, reveal) share `cubic-bezier(.32,.72,0,1)` at 320–420ms; panels slide in and out with a CSS transform transition on the same curve (`--base`, 300ms) and unmount when it ends. Reduced motion sets both to zero duration, so panels appear and disappear at their resting position with no painted travel.
|
|
187
|
+
- Zoom and fit scale the complete authored node as one unit. No zoom level hides subtitles, fields, attributes, methods, stereotypes, or other authored node text; fullscreen fit follows the same rule. Centered shape text wraps inside the available rectangle, with narrower areas for diamonds, ellipses, pills and slanted shapes. `layoutText()` never cuts a token (text between spaces) that fits a line, so ordinary text wraps as before; a token wider than the line is cut between CJK characters first, then at an identifier's own separators (`_ . / - = , : ;` and camelCase humps), and only then anywhere, and closing punctuation never starts a line while opening punctuation never ends one (its neighbour travels with it). Shared width estimates reserve space for uppercase identifiers and wide Latin letters. Boundary titles reserve space for fragment notation. The `text-bounds` browser check covers every supported node kind, long fields and members, boundary titles, uppercase edge labels, and all three zoom levels, and fails if any non-empty authored node text has computed opacity zero.
|
|
188
|
+
- Quick look tries right → left → below → above using its actual size. Ordinary graphs retain minimum-overlap fallback. Sequence cards additionally avoid message labels, stroke/arrow bands and branch guards. If no safe candidate fits, hand the same selection to the existing Inspector and pan without shrinking; never place a covering card. Card and Inspector share one editor draft and save validation, including across resize/fallback.
|
|
189
|
+
- Long quick-look content wraps and scrolls. Fullscreen detail handoff exits fullscreen before opening Inspector. A rejected exit preserves selection and reports failure; safe cards stay usable, while unsafe sequence cards remain hidden and the fullscreen exit control provides retry.
|
|
190
|
+
- The legend button and zoom controls move to 328px from the left while the navigation panel is open; the minimap moves to 328px from the right while the Inspector is open (`.workspace.nav-open / .drawer-open`); at 700px and below they stay put.
|
|
191
|
+
- SVG and PNG exports contain the complete diagram rather than only the current viewport.
|
|
192
|
+
|
|
193
|
+
### Responsive layout
|
|
194
|
+
|
|
195
|
+
- At every width both floating panels start collapsed and open as 304px overlays (toolbar bottom + 12px to window bottom − 12px) without a backdrop, so the canvas stays pannable beside them. Escape closes an open popover first, then the panel holding focus (returning focus to its toolbar toggle), then the selection. Do not remove evidence access at an intermediate breakpoint.
|
|
196
|
+
- At 700px and below, a panel is the window width minus 24px, opening one closes the other, the minimap is hidden. Selecting a search result opens its details and closes the navigation panel.
|
|
197
|
+
- Keep mobile actions reachable, retain a visible canvas beside an open panel, and prevent horizontal page overflow at 390px. The Inspector still shows complete text and scrolls vertically.
|
|
198
|
+
|
|
199
|
+
### Evidence display
|
|
200
|
+
|
|
201
|
+
- Keep node identity, fields, attributes, methods, source anchors and tags first in the Inspector, then a separate final facts section from the same selected node. Show `Evidence facts` with a source or `Node notes` without one. Omit this section when there are no facts or no inspected node. It scrolls with the Inspector content.
|
|
202
|
+
- Direct source, configuration, schema, test, or document evidence uses a solid baseline unless the relationship notation requires a dashed line, such as a return, dependency, or implementation.
|
|
203
|
+
- Framework behavior and inference retain a dashed baseline, except synchronous sequence messages, which stay solid. Keep evidence in metadata/details and preserve each relationship's notation during flow.
|
|
204
|
+
- The detail drawer says `Evidence` rather than claiming every anchor is source code.
|
|
205
|
+
- Facts, subtitles, fields, methods, source symbols, and tags wrap long tokens within the Inspector. Preserve complete text and allow only vertical scrolling.
|
|
206
|
+
|
|
207
|
+
### Browser acceptance
|
|
208
|
+
|
|
209
|
+
- Run the full browser matrix only when Viewer source, edge routing, graph schema, or validation behavior changed. Cover all nine diagram types at 1440×900, 1920×1080, and 390×844 in light and dark themes; check default directed flow, absence of playback controls, stable manual selection with still-moving edges, the independent flow switch, dynamic semantic legends, pan/zoom, ranked search, linked node/edge emphasis without geometry changes or duplicate pulses, complete Inspector wrapping, layout lock and spacing-result feedback, reset, SVG/PNG download without transient effects, keyboard use, reduced motion, and horizontal overflow. Check each type's own notation and core emphasis. Standalone pages have no diagram-type menu; requested collections use a vertical view menu. At 700px and below both panels start collapsed, remain accessible, and open mutually exclusively.
|
|
210
|
+
- Open downloaded images to check complete labels, notation, uncropped boundaries, and theme parity. Reuse installed browser tooling and close temporary HTTP servers in `finally`.
|
|
211
|
+
|
|
212
|
+
### Sequence rendering and interaction
|
|
213
|
+
|
|
214
|
+
- Full lifelines remain in routing/export bounds, but transparent columns and lifelines never intercept pointers. Visible heads and message label buttons are the interactive targets; labels, lines and Enter/Space share one selection entry point.
|
|
215
|
+
- Sequence labels retain all text and clear message strokes by at least 6 graph units. Explicit label positions remain authoritative and are validated. Invalid edits retain the old graph and editable draft. Self-call labels use the actual routed extent.
|
|
216
|
+
- Participant subtitles appear completely in shared SVG drawing; automatic layout measures their width and strict export rejects truncated legacy geometry. Sync uses a filled arrow, async an open V, return an open V plus dashes; sync remains solid independently of evidence. Legend and exports reflect actual kinds.
|
|
217
|
+
- `sequence-fragments.js` validates and lays out explicit alt/opt/loop/par operands for page/export. Legacy alt frames visibly disclose missing conditions and emit warnings. Never infer operands from message names.
|
|
218
|
+
- A single native media-query subscription owns live reduced-motion state. Remove sequence flow overlays and disable/explain the switch while reduced; restore the user's previous flow choice when cleared, without resetting graph, selection, panels or viewport.
|
|
219
|
+
|
|
220
|
+
### State notation
|
|
221
|
+
|
|
222
|
+
`diagrams/state.js` owns the state shape: the title compartment, the optional `entry` / `do` / `exit` compartment (`stateActivities`, `activityArea`, measured by `layout-measure.js`), the label roles (`edgeLabelParts`, cut into wrapped lines by `labelRunsByLine`, colored through `labelRoleColors`) and the legend wording (`legend`). Self-transitions are laid out as a polyline and smoothed into an arc by `pathFromRoute` (`curvedSelfLoops`); audits keep seeing the polyline. Page and export share all of it; the export check treats the action compartment as its own safe area.
|
|
223
|
+
|
|
224
|
+
State colors come from the lifecycle, not from the module (one machine is one module, so the module says nothing there). `stateToneRoles()` in `visual-style.js` derives a role for every `state` node from facts the graph already has: the `core` state is the goal (Grass), a state tagged `failure` is failed (Red), a state whose every way out ends the machine, or that has none, is ended (Slate), and the rest are in flight and take the ramp Orange, Blue, Teal, Violet, Plum, Indigo in the order the machine reaches them (distance from `initial`, ties by declaration order, wrapping after six). Without a `core` state nothing is derived and the states keep their module colors. `moduleColorMap()` returns the roles on the map as `stateTones` (a collection has one state view, so the node id is enough for every `nodeAppearance()` caller); `PALETTES[theme].stateTones` holds the colors; `withoutWash()` is the card-wash switch for modules and state tones together. A state tone replaces the module's frame and wash on the card; the core ring, the module chip and every line leaving the card keep their roles. The legend lists the tones in use, with one ramp entry for the in-flight states; the tests hold every tone to ink ≥4.5:1 on its body, a ≥3:1 frame and ≥12 ΔE between frames in both themes.
|
|
225
|
+
|
|
226
|
+
### Sequence execution and motion checks
|
|
227
|
+
|
|
228
|
+
`sequence-executions.js` owns explicit pairs, endpoint intervals and nesting; routing, participant drawing, exports and bounds consume the same geometry. `sequence-fragments.js` owns operand ancestry, text and collision checks. Fragment text is drawn above lifelines with an opaque text background; titles avoid execution bars. Pair IDs have a group-colored underline with readable neutral text. New validator imports must be included in `package.json` files.
|
|
229
|
+
|
|
230
|
+
Nodes, routes, arrows and stroke widths scale together. Sequence screen dash periods have a 6 CSS px minimum to avoid low-DPI aliasing; baseline, mask and phase share this adjustment. Static SVG/PNG exports retain graph-unit notation. Sequence motion uses a 3.2-unit stroke over a 1.6-unit baseline; other directed edges use 2.8 units. Both the main rules and the motion-effects block at the end of `styles.css` participate. The graph collection owns the user's flow choice so switching diagrams preserves it. Keep live reduced-motion behavior. Sync uses a solid baseline independently of evidence; non-sequence notation and undirected kinds remain unchanged. Default include/extend labels sit beside their path so short use-case relationships remain visible; explicit label positions still win.
|
|
231
|
+
|
|
232
|
+
Run `node --test tests/*.test.mjs skills/q-flow/scripts/*.test.mjs`, build the Viewer, generate the sample and use `QA_ONLY_EXTRAS=1 QA_EXTRAS=motion-matrix node skills/q-flow/scripts/browser-interactions.mjs GENERATED REPORT` for actual screenshot profiles and video. Nine types × two themes × three viewports give 54 cases. Check default and selected overview plus readable local paths for every actual line kind, opposite directions and self messages. ER is a static control. A CSS clock or structure validation does not prove perceptible movement. Preserve historical media recording hashes; write a new report.
|
|
233
|
+
|
|
234
|
+
Use `QA_EXTRAS=motion-preferences` for user flow choice, live reduced motion, diagram switching, high contrast and reduced transparency. `QA_TYPES` and `QA_WIDTHS` narrow matrix retries. Frame sampling excludes labels, overlaid edges and bends; correlate the changing ink across 0/70/140 ms and retain the actual images for review.
|
|
235
|
+
|
|
236
|
+
### Landscape composition and proportional motion acceptance
|
|
237
|
+
|
|
238
|
+
Initial canvas budgets are 2400×1600 graph units for architecture / er / deployment / class / usecase / dataflow, 1600×2400 for flowchart / state, and content-adaptive for sequence. These are starting budgets, not minimum borders, export resolutions or aspect-ratio requirements. Preserve full text and type semantics; expand for spacing and routes, keep small graphs compact, and never shrink text or add empty padding to match a ratio. Start with 64 units between peers and 80–96 between layers; expand only the affected label/port corridor. Keep 24 around labels and below measured group headings, with 32 at group sides. Wrap complete long labels instead of spreading every node; sequence messages constrain their own participant span and measured row height. Budgets alone do not rearrange existing geometry.
|
|
239
|
+
|
|
240
|
+
Generation and validation share `layoutComposition.canvasBudget`. Sequence reports `canvasBudget`, `targetRatio`, `fit`, `aspectBand` and `withinBand` as null. `aspectBand` is 1.6, `bandSlack` is 1.1 and `withinBand` says whether the width/height ratio sits within that slack of the band 1/1.6–1.6; a small graph may legitimately sit outside, so the report stays informational and never triggers an aspect-ratio warning. Actual content bounds still control fit and SVG/PNG dimensions; reporting the budget does not perform automatic layout or certify readability.
|
|
241
|
+
|
|
242
|
+
Nodes, routes, arrows and stroke widths scale together. Sequence screen dash periods have a 6 CSS px minimum to avoid low-DPI aliasing; baseline, mask and phase share this adjustment. Static SVG/PNG exports retain graph-unit notation.
|
|
243
|
+
|
|
244
|
+
- Automation handle: a page opened with `?automation=1` exposes `window.__qgraphflowAutomation = { getViewport, setViewport, setCenter, ease }` (the React Flow viewport API plus the shared easing) for recording and QA drivers. Ordinary pages expose nothing; it never changes rendering.
|
|
245
|
+
|
|
246
|
+
## adaptive-v2
|
|
247
|
+
|
|
248
|
+
`adaptive-v2`: generation defaults to `--layout auto` and accepts semantic inputs without positions or sizes. Declare real ownership with node `groupId` and group `parentId`; color `module` is not ownership. Optional node `layout.rank` / `layout.order`, graph `layout.primaryPath` / `layout.participantOrder` express existing order only. `--layout preserve` checks existing geometry without rearranging it. Ambiguous old containment requires an explicit author decision. `--input-only` checks semantics; source, geometry and browser rendering have separate statuses. `--force` only permits replacing outputs and never bypasses quality checks. A layered result whose width/height ratio leaves the accepted band 1/1.6–1.6 by more than 10% is folded (2–5 segments): a top-down layout that is too tall cuts its layer sequence into columns with aligned tops, a left-to-right layout that is too wide cuts it into rows with aligned starts, so every segment keeps its reading direction and continues at the start of the next one. Geometry and routes inside a segment are kept; cuts prefer boundary changes and avoid a decision's branches; an edge across a cut runs through the channel between its segments, or through the corridor before or after them when a neighbour or heading is in the way. A boundary spread over several segments is rebuilt around its members and must not cover foreign nodes. The fewest segments that pass the quality gate within 10% of the band win, so a balanced fold is not passed over for a ragged one; a fold the gate rejects is skipped for the next one, and otherwise the nearest valid shape competes with the unfolded result. The type budget only breaks ties towards its preferred orientation. Declared `layout.rank` layouts and state charts with branches or loops never fold.
|
|
249
|
+
|
|
250
|
+
Edge labels sit on their own line (ELK places them inline; the shared router centres them on the clearest segment), except sequence messages, which stay above the arrow. Keep full text at 20/16/14px. Minimum clearances: nodes 48px; labels to nodes/labels 24px; labels to unrelated edges 6px; below measured group heading 24px, other insets 32px; sibling groups 48px; parallel channels 24px; straight endpoint segments 12px, ER 28px. Readable point crossings, including nonplanar graphs, are allowed. Prefer fewer repeated crossings between the same pair and reject long collinear overlaps. Layout generation also removes crossings that a different order would avoid: while a candidate still crosses, a bounded search (`REFINE_*` in `compile-layout.mjs`) swaps the ports of the crossing relations within a node side, the side a decision branch or actor relation leaves on, sibling node order and fold lanes, and keeps a swap only when the candidate scores better, so only the crossings a topology forces remain (a full 3×3 mesh keeps 9). Authors never need to reorder edges or add hints for this. Canvas ratios are informational; never remove relationships or shrink text to pass. Ordering rules (main path, class hierarchy, state endpoints) accept a successor that continues at the top of the next column.
|
|
251
|
+
|
|
252
|
+
Sequence `order` retains semantic order; generated `route.messageY` supplies the actual vertical coordinate. Outside `--input-only`, every sequence message needs `route.messageY`; automatic layout writes it, so regenerate older graphs with `--layout auto`. Automatic layout requires explicit fragment operands and never invents conditions. Participant subtitles, guards and bodies must be complete. Invalid geometry remains an editable/saveable JSON draft; SVG/PNG export checks the current snapshot and actual glyph bounds after fonts load. PNG rejects blank encoding or sizes above 32767px / 64 million pixels, without reducing resolution.
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
node scripts/validate-graph.mjs graph.json --input-only
|
|
256
|
+
node scripts/generate-viewer.mjs graph.json output-directory --layout auto
|
|
257
|
+
node scripts/validate-graph.mjs output-directory/graph.json
|
|
258
|
+
```
|