flowsvg 0.1.0 → 0.1.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +26 -1
  2. package/PATTERNS.md +12 -1
  3. package/README.md +139 -117
  4. package/README.zh-CN.md +139 -117
  5. package/dist/adapters/react.d.ts +48 -0
  6. package/dist/adapters/react.js +126 -0
  7. package/dist/adapters/vue.d.ts +132 -0
  8. package/dist/adapters/vue.js +161 -0
  9. package/dist/adapters/web-component.d.ts +99 -0
  10. package/dist/adapters/web-component.js +396 -0
  11. package/dist/browser/player.d.ts +69 -0
  12. package/dist/browser/player.js +353 -0
  13. package/dist/browser.d.ts +4 -69
  14. package/dist/browser.js +2 -353
  15. package/dist/core/color.d.ts +3 -0
  16. package/dist/core/color.js +18 -0
  17. package/dist/core/diagram.d.ts +38 -0
  18. package/dist/core/diagram.js +391 -0
  19. package/dist/core/edge-geometry.d.ts +32 -0
  20. package/dist/core/edge-geometry.js +382 -0
  21. package/dist/{core.d.ts → core/model.d.ts} +2 -52
  22. package/dist/core/model.js +1 -0
  23. package/dist/{node-content-paint.d.ts → core/node-content-paint.d.ts} +2 -2
  24. package/dist/{node-content-paint.js → core/node-content-paint.js} +11 -20
  25. package/dist/{node-content.d.ts → core/node-content.d.ts} +1 -1
  26. package/dist/core/svg-number.d.ts +5 -0
  27. package/dist/core/svg-number.js +7 -0
  28. package/dist/{theme.d.ts → core/theme.d.ts} +6 -0
  29. package/dist/core/theme.js +17 -0
  30. package/dist/{timeline.d.ts → core/timeline.d.ts} +1 -3
  31. package/dist/{timeline.js → core/timeline.js} +1 -17
  32. package/dist/core/validation.d.ts +11 -0
  33. package/dist/core/validation.js +389 -0
  34. package/dist/index.d.ts +5 -1
  35. package/dist/index.js +2 -1
  36. package/dist/react.d.ts +3 -48
  37. package/dist/react.js +2 -126
  38. package/dist/vue.d.ts +3 -132
  39. package/dist/vue.js +2 -161
  40. package/dist/web-component.d.ts +3 -103
  41. package/dist/web-component.js +2 -396
  42. package/docs/images/entity-graph.png +0 -0
  43. package/docs/images/facts-timeline.png +0 -0
  44. package/docs/images/facts-timeline.zh-CN.png +0 -0
  45. package/docs/images/node-content.png +0 -0
  46. package/docs/images/numbered-list.png +0 -0
  47. package/docs/images/numbered-list.zh-CN.png +0 -0
  48. package/docs/images/quick-start.png +0 -0
  49. package/docs/images/record-tags.png +0 -0
  50. package/package.json +2 -1
  51. package/dist/core.js +0 -1149
  52. package/dist/theme.js +0 -6
  53. /package/dist/{player-accessibility.d.ts → browser/accessibility.d.ts} +0 -0
  54. /package/dist/{player-accessibility.js → browser/accessibility.js} +0 -0
  55. /package/dist/{player-controls.d.ts → browser/controls.d.ts} +0 -0
  56. /package/dist/{player-controls.js → browser/controls.js} +0 -0
  57. /package/dist/{player-lifecycle.d.ts → browser/lifecycle.d.ts} +0 -0
  58. /package/dist/{player-lifecycle.js → browser/lifecycle.js} +0 -0
  59. /package/dist/{svg-dom.d.ts → browser/svg-dom.d.ts} +0 -0
  60. /package/dist/{svg-dom.js → browser/svg-dom.js} +0 -0
  61. /package/dist/{node-content.js → core/node-content.js} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1 - 2026-10-20
4
+
5
+ - Adopted thin neutral node borders, gray edges and blue emphasis in both themes. Separate highlight fills from borders and flow markers, remove the node-label halo, and refresh README previews.
6
+
7
+ - Expanded the quick-start document to a six-node order workflow with authorization, persistence, asynchronous events and reverse responses.
8
+
9
+ - Added SDK-rendered previews beside the quick-start, structured-content, list, entity-graph and Facts timeline examples in both package READMEs.
10
+
11
+ - Include the eight README preview images in the npm tarball. Source READMEs use relative image paths for local previews; the release verifier prepares staged README copies with versioned npm CDN URLs for public readers. Release verification requires every preview and rejects undeclared documentation assets.
12
+
13
+ - Removed local build, source-layout and release instructions from the package READMEs; repository documentation retains the contributor workflow.
14
+
15
+ - Completed bilingual module navigation, updated migrated test links, and consolidated architecture implementation and verification records.
16
+
17
+ - Grouped React, Vue, and Web Component implementations under `src/adapters/` while keeping explicit root entries, shared Web Component registration identity, SSR-safe imports, lifecycle behavior, and cross-host integration tests. Updated nested release artifacts and consumer references.
18
+
19
+ - Grouped the browser player implementation and its controls, accessibility, lifecycle, and SVG DOM modules under `src/browser/` while preserving the `flowsvg/browser` entry, lifecycle behavior, DOM-free imports, and public player contract. Updated browser tests and release allowlists for nested build output.
20
+
21
+ - Moved shared hexadecimal color interpolation to the pure `src/core/color.ts` module. Timeline evaluation and diagram paint share the same runtime helper; content painting checks contrast without a timeline dependency.
22
+
23
+ - Centralized edge routing, path sampling, distance queries, label anchors, hit testing, and SVG number formatting in `src/core/edge-geometry.ts` and `src/core/svg-number.ts` while preserving the existing public frames and SVG output.
24
+
25
+ - Organized the platform-independent core into shared model, document validation and diagram orchestration modules. Preserved all public values/types, error identity and evaluation behavior; grouped core tests with their implementation.
26
+ - Preserved nested JavaScript and declaration build output and replaced the release file pattern with an explicit module allowlist that rejects source, tests and unrelated files.
27
+
3
28
  ## 0.1.0 - 2026-10-17
4
29
 
5
30
  - Added a release CLI that runs type checks, SDK and release-script tests, tarball consumer verification, example builds and npm publication preview. Default to preview; require `--publish` for upload, and publish the exact verified tarball retained in `dist/releases/`.
@@ -9,7 +34,7 @@
9
34
  - Prepared the first npm release with repository metadata, public registry settings, bilingual package documentation, automatic prepack builds and prepublish type/test checks.
10
35
  - Verify packaging from a missing `dist`, every exported JavaScript/type/CSS target, the file allowlist, framework-free imports, strict consumer types and all four host lifecycles.
11
36
 
12
- - Renamed the package and SDK directory to `flowsvg` (流形). Player exports now use the `Flow` prefix: `FlowPlayer`, `FlowPlayerProps`, `FlowPlayerHandle`, `FlowPlayerElement`, `defineFlowPlayer()` and `FLOW_PLAYER_TAG`. The default element is `<flowsvg-player>`; CSS classes and control variables use `flowsvg-` and `--flowsvg-`. Imports, selectors and public API references must use the new names.
37
+ - Renamed the package and SDK directory to `flowsvg`. Player exports now use the `Flow` prefix: `FlowPlayer`, `FlowPlayerProps`, `FlowPlayerHandle`, `FlowPlayerElement`, `defineFlowPlayer()` and `FLOW_PLAYER_TAG`. The default element is `<flowsvg-player>`; CSS classes and control variables use `flowsvg-` and `--flowsvg-`. Imports, selectors and public API references must use the new names.
13
38
 
14
39
  - Fixed cylinder SVG fills by rendering the top cap as a separate filled ellipse inside the same node interaction group, so the cap no longer appears hollow.
15
40
 
package/PATTERNS.md CHANGED
@@ -1,12 +1,23 @@
1
1
  # Patterns
2
2
 
3
+ - Keep default diagram colors and line metrics in the core theme. Use separate highlight fill, border/marker and marker-text colors so active nodes retain readable text in both themes. Browser hover consumes the same theme values; preserve border-only hover by default. Node labels have no halo; edge labels retain background isolation where paths cross text.
4
+
5
+ - Keep README previews in `docs/images/` inside the SDK package. Package only PNG assets and enumerate the expected images in the release verifier; require each image and reject undeclared files. Use relative image paths in source READMEs for local previews. The release verifier unpacks the built package into a temporary directory and converts only its README copies to npm CDN URLs with the package version. Repack without lifecycle scripts, require the packed file set to remain unchanged, then verify and retain that final tarball. Source READMEs must remain unchanged by release verification.
6
+
7
+ - Keep shared document/frame types in `src/core/model.ts`, document validation and its single error class in `validation.ts`, and compilation, cloning/freezing, evaluation, SVG and hit-test orchestration in `diagram.ts`. Node-content types and nested validation remain in `node-content.ts`. Import runtime owners directly; use type imports for shared model dependencies.
8
+ - Enumerate public values and types in the root entry, including `wrapText`, `Diagram` and all node-content types. Keep core contract tests in `src/core/` and consume public entries. Four entry-specific TypeScript builds preserve the internal directory hierarchy under `dist`; enumerate each emitted module in the release allowlist instead of accepting arbitrary files below `dist`.
9
+ - Keep the browser player implementation under `src/browser/player.ts`, with controls, accessibility, lifecycle, and SVG DOM synchronization beside it. Keep `src/browser.ts` as the explicit public entry; browser tests use that entry while core remains DOM-free.
10
+ - Keep React, Vue, and Web Component implementations under `src/adapters/`; root adapter files are explicit public entries that export the single implementation and preserve runtime identity. Adapter tests live beside their implementations and cross-host business scenarios live under `tests/integration/`; imports use public entries.
11
+ - Keep color interpolation in the pure `src/core/color.ts` module. Timeline and diagram painting import it directly so generic color rules do not depend on timeline semantics. Content highlight paint retains theme-specific semantic colors and adjusts default ink only when the evaluated fill requires more contrast.
12
+ - Keep route construction, path measurements, label anchors, and path hit testing in `src/core/edge-geometry.ts`; share integer and three-decimal formatting through `src/core/svg-number.ts`. Diagram orchestration consumes these internal results without exposing geometry.
13
+
3
14
  - Run release gates sequentially through the root release CLI. Default to a preview without authentication; check authentication before the gates only for explicit publication. Copy the verified tarball after all consumer checks pass, then preview and publish that file without repacking. Stop on any command failure and keep npm stdio attached for authentication prompts.
4
15
 
5
16
  - Build Vue declarations against the pinned minimum supported version, 3.5.0; newer Vue patch releases can add inferred `DefineComponent` type parameters that older consumers cannot read. Verify both minimum and current framework versions in the packed-package consumer.
6
17
 
7
18
  - Build through `prepack` so `npm pack` and directory publishing cannot use missing or stale artifacts. Keep publish-only type/test checks separate from packaging to avoid recursive release verification. Verify the packed exports and strict consumer types before publishing.
8
19
 
9
- - Publish the SDK as `flowsvg` from `packages/flowsvg`. Use the `Flow` prefix for player exports, `<flowsvg-player>` for the default custom element, and `flowsvg-` / `--flowsvg-` for CSS classes and control variables. Keep package imports, declarations, examples and release verification aligned with these names.
20
+ - Use FlowSVG as the display name. Publish the SDK as `flowsvg` from `packages/flowsvg`. Use the `Flow` prefix for player exports, `<flowsvg-player>` for the default custom element, and `flowsvg-` / `--flowsvg-` for CSS classes and control variables. Keep package imports, declarations, examples and release verification aligned with these names.
10
21
 
11
22
  - Apply hover to stroke only by default for every node and group. Preserve core fill and content paint, including active flow or explicit highlights. With `hoverFill: true`, paint fill and readable content as well. Forward the same option through every adapter and restore the current core SVG when the pointer leaves.
12
23
 
package/README.md CHANGED
@@ -1,12 +1,12 @@
1
- # flowsvg
1
+ # FlowSVG
2
2
 
3
3
  English | [简体中文](README.zh-CN.md)
4
4
 
5
- flowsvg (流形) is a TypeScript ESM SDK for declarative SVG diagram playback. Describe logic and architecture diagrams with nodes, edges, scenes, and beats in JSON or TypeScript objects, then play the diagram in a browser or export an SVG at a specific time without a DOM.
5
+ FlowSVG plays logic and architecture diagrams with SVG. Define nodes, edges, scenes, and beats in JSON or TypeScript, then play them in a browser or export a static SVG.
6
6
 
7
- The core handles validation, time evaluation, geometry, and SVG output. Browser and framework adapters handle playback, interaction, and lifecycle. The same document, time, and visual options produce the same core frame. Node hover adds temporary browser feedback.
7
+ The ESM SDK includes a DOM-free core and native browser, Vue, React, and Web Component integrations. All integrations share the same document and timeline.
8
8
 
9
- [Installation](#installation) · [Quick start](#quick-start) · [Vue](#vue-integration) · [React](#react-integration) · [Native browser](#native-browser) · [Web Component](#web-component) · [API](#api) · [Development](#development-and-verification) · [License](#license)
9
+ [Installation](#installation) · [Quick start](#quick-start) · [Vue](#vue-integration) · [React](#react-integration) · [Native browser](#native-browser) · [Web Component](#web-component) · [API](#api) · [License](#license)
10
10
 
11
11
  ## Installation
12
12
 
@@ -27,16 +27,7 @@ pnpm add react react-dom
27
27
  pnpm add -D @types/react @types/react-dom
28
28
  ```
29
29
 
30
- The core and native browser entries require neither framework. To build a local tarball before publication, run from the repository root:
31
-
32
- ```sh
33
- pnpm install --frozen-lockfile
34
- pnpm --filter flowsvg exec npm pack --pack-destination /tmp
35
- ```
36
-
37
- The packaging hook builds the SDK. Install the resulting `0.1.0` tarball with `pnpm add /tmp/flowsvg-0.1.0.tgz`.
38
-
39
- The core requires Node.js 18+ or an environment supporting ES2022 ESM. Browser playback requires a DOM, SVG, and `requestAnimationFrame`. Minimum framework versions are Vue 3.5 and React 18. TypeScript declarations are included; React TypeScript applications also need the matching `@types/react` and `@types/react-dom` packages.
30
+ The core and native browser entries need no framework. TypeScript declarations are included. Core: Node.js 18+ or ES2022 ESM. Browser: DOM, SVG, and `requestAnimationFrame`. Frameworks: Vue 3.5+ or React 18+.
40
31
 
41
32
  | Import path | Purpose |
42
33
  | --- | --- |
@@ -51,31 +42,63 @@ The core requires Node.js 18+ or an environment supporting ES2022 ESM. Browser p
51
42
 
52
43
  ### 1. Create a diagram document
53
44
 
54
- Save this as `diagram.ts`. The integration examples below reuse this file:
45
+ Save this as `diagram.ts`. This 5.2-second example uses six nodes, three scenes and five beats. It separates authorization, order persistence, and the final response with an asynchronous event. The integration examples below reuse this file:
55
46
 
56
47
  ```ts
57
48
  import type { DiagramDocument } from "flowsvg";
58
49
 
59
50
  export const diagramDocument: DiagramDocument = {
60
51
  version: 1,
61
- title: "Request flow",
62
- viewport: { width: 640, height: 280 },
52
+ title: "Order request lifecycle",
53
+ viewport: { width: 960, height: 440 },
63
54
  nodes: [
64
- { id: "client", kind: "roundedRect", x: 40, y: 100, width: 140, height: 80, label: "Client" },
65
- { id: "service", kind: "rect", x: 420, y: 100, width: 160, height: 80, label: "Service", visible: false }
55
+ { id: "client", kind: "roundedRect", x: 40, y: 205, width: 120, height: 70, label: "Client" },
56
+ { id: "gateway", kind: "roundedRect", x: 260, y: 205, width: 130, height: 70, label: "API gateway" },
57
+ { id: "auth", kind: "ellipse", x: 260, y: 40, width: 130, height: 72, label: "Auth" },
58
+ { id: "orders", kind: "roundedRect", x: 500, y: 205, width: 150, height: 70, label: "Order service" },
59
+ { id: "database", kind: "cylinder", x: 780, y: 65, width: 140, height: 90, label: "Orders" },
60
+ { id: "events", kind: "rect", x: 780, y: 330, width: 140, height: 70, label: "Event queue" }
66
61
  ],
67
- edges: [{ id: "request", from: "client", to: "service", label: "HTTP", route: "straight" }],
68
- scenes: [{
69
- id: "request-flow",
70
- label: "Request reaches the service",
71
- beats: [
72
- { durationMs: 1000, transitionMs: 250, caption: "Show the service", actions: [{ type: "show", target: "service" }] },
73
- { durationMs: 1000, caption: "The service handles the request", actions: [{ type: "highlight", target: "service" }] }
74
- ]
75
- }]
62
+ edges: [
63
+ { id: "request", from: "client", to: "gateway", route: "straight" },
64
+ { id: "authorize", from: "gateway", to: "auth", route: "straight", fromPort: "top", toPort: "bottom" },
65
+ { id: "create", from: "gateway", to: "orders", route: "straight" },
66
+ { id: "persist", from: "orders", to: "database", route: "orthogonal", fromPort: "right", toPort: "left" },
67
+ { id: "publish", from: "orders", to: "events", route: "curved", fromPort: "bottom", toPort: "left" },
68
+ { id: "result", from: "orders", to: "gateway", route: "curved", fromPort: "bottom", toPort: "bottom" }
69
+ ],
70
+ scenes: [
71
+ { id: "request", label: "Receive and authorize", beats: [
72
+ { durationMs: 800, caption: "Receive the order request", actions: [
73
+ { type: "flow", target: "request", direction: "forward", text: "POST" }
74
+ ] },
75
+ { durationMs: 800, caption: "Validate the access token", actions: [
76
+ { type: "flow", target: "authorize", direction: "forward", text: "Token" }
77
+ ] }
78
+ ] },
79
+ { id: "create-order", label: "Create the order", beats: [
80
+ { durationMs: 1000, caption: "Send the authorized request to the order service", actions: [
81
+ { type: "flow", target: "create", direction: "forward", text: "Create" }
82
+ ] },
83
+ { durationMs: 1000, caption: "Persist the new order", actions: [
84
+ { type: "flow", target: "persist", direction: "forward", text: "INSERT" }
85
+ ] }
86
+ ] },
87
+ { id: "response", label: "Publish and respond", beats: [
88
+ { durationMs: 1600, caption: "Publish OrderCreated and return the order number", actions: [
89
+ { type: "flow", target: "publish", direction: "forward", text: "OrderCreated" },
90
+ { type: "flow", target: "result", direction: "forward", text: "201 Created" },
91
+ { type: "flow", target: "request", direction: "reverse", text: "Order ID" }
92
+ ] }
93
+ ] }
94
+ ]
76
95
  };
77
96
  ```
78
97
 
98
+ At 4400 ms with the light theme, the event and response messages move together. Only their participating nodes are highlighted.
99
+
100
+ ![Order workflow with Client, API gateway, Auth, Order service, Orders database and Event queue; OrderCreated and response messages are in flight](https://cdn.jsdelivr.net/npm/flowsvg@0.1.1/docs/images/quick-start.png)
101
+
79
102
  ### 2. Evaluate and export SVG
80
103
 
81
104
  ```ts
@@ -87,10 +110,10 @@ const frame = diagram.evaluateAt(125);
87
110
  console.log(diagram.totalDuration, frame.sceneId, frame.warnings);
88
111
 
89
112
  const svg = diagram.renderSvg(125, { theme: "dark" });
90
- const finalSvg = renderSvg(diagramDocument, 2000);
113
+ const finalSvg = renderSvg(diagramDocument, 5200);
91
114
  ```
92
115
 
93
- `svg` and `finalSvg` are complete static SVG strings, ready to save to a file or preview. Reuse the result of `createDiagram` for repeated queries; use `renderSvg` for a single export. Choose one of the integrations below for browser playback.
116
+ Both results are static SVG strings. Reuse `createDiagram` for repeated queries; use `renderSvg` for a single export.
94
117
 
95
118
  ## Vue integration
96
119
 
@@ -127,7 +150,7 @@ function handleError({ error }: PlayerErrorDetail): void {
127
150
  </template>
128
151
  ```
129
152
 
130
- The component destroys the player on unmount. Use `player.value` to call handle methods in script; templates unwrap the ref automatically. Events are `stepchange`, `timeupdate`, `playstatechange`, and `error`, with the event detail passed directly as the argument. Corresponding callback props are also available.
153
+ Unmounting destroys the player. In scripts, call methods through `player.value`; templates unwrap the ref. Events pass their detail directly: `stepchange`, `timeupdate`, `playstatechange`, and `error`. Callback props are also supported.
131
154
 
132
155
  ## React integration
133
156
 
@@ -171,9 +194,9 @@ The component releases resources on unmount. Callbacks are `onStepChange`, `onTi
171
194
  | `initialTimeMs` | Applied only on initial mount; use `seek` for later changes |
172
195
  | `className` / `style` | Host container styles |
173
196
 
174
- Changing `theme`, `controls`, `reducedMotion`, or `hoverFill` recreates the player, paused at zero; `initialTimeMs` is not reapplied. Provide a new object reference when changing the document. The ref / exposed handle provides the seven control and state methods in the Player API below; the component owns cleanup. Calling a retained handle before mounting or after unmounting throws a not-mounted error.
197
+ Changing `theme`, `controls`, `reducedMotion`, or `hoverFill` recreates the player, paused at zero, without reapplying `initialTimeMs`. Reload documents with a new object reference. Handles expose seven methods listed under [Player API](#player-api); calls before mount or after unmount throw.
175
198
 
176
- Both framework entries are safe to import on the server. SSR renders a host container, and the player is created after client mounting. Use the core `renderSvg` for a server-rendered diagram preview.
199
+ Both entries support server-side imports. SSR renders a container; the player is created on client mount. Use `renderSvg` for server-side diagram previews.
177
200
 
178
201
  ## Native browser
179
202
 
@@ -197,11 +220,9 @@ const player = createPlayer(host, diagramDocument, { theme: "light" });
197
220
  player.play();
198
221
  ```
199
222
 
200
- `createPlayer` takes over the container and replaces its children. Call `player.destroy()` when the page or component is disposed, and destroy the old instance before creating another player in the same container. Bare imports in plain HTML need an import map or application build tooling; the repository's native example includes an import map.
223
+ `createPlayer` replaces the container children. Call `destroy()` on disposal and before reusing the container. Plain HTML requires an import map or build tooling for package imports.
201
224
 
202
- The repository example at `apps/native-example/` keeps the camera and layout fixed throughout an order request and response. At 0–2400 ms, three curved edges carry a dot, a `Token` capsule, and a reverse precheck capsule. At 2400–4800 ms, two other curves carry the order request and inventory confirmation. At 4800–7200 ms, response messages return to the gateway and client; the final beat ends at 8200 ms with no temporary highlights. Opposite relationship edges remain separate, and hovering a participant adds only a browser highlight.
203
-
204
- Reuse `apps/native-example/request-chain.js` as the `document` input for native, Web Component, React, or Vue integration. Each host uses the same `seek(timeMs)`, `setSpeed(speed)`, `play()`, and `pause()` behavior. `src/business-integration.test.ts` verifies this shared example through all four public entries, including both themes, reduced motion, beat boundaries, and core SVG equality.
225
+ The [native example](https://github.com/yokingma/FlowSVG/blob/HEAD/apps/native-example/README.md) demonstrates request/response flows. Its shared document also works with the other three integrations.
205
226
 
206
227
  ## Web Component
207
228
 
@@ -221,13 +242,13 @@ const element = document.querySelector<FlowPlayerElement>("flowsvg-player");
221
242
  if (element) element.document = diagramDocument;
222
243
  ```
223
244
 
224
- The default element name is `flowsvg-player`; repeated registration is safe. Pass objects through the `document` property. The HTML `document` attribute accepts a JSON string; `src` accepts a JSON URL resolved relative to the page. URL requests follow browser same-origin and CORS rules.
245
+ Registration defaults to `<flowsvg-player>` and is idempotent. Assign objects to `element.document`, JSON strings to the HTML `document` attribute, or a JSON URL to `src`. URLs resolve against the page and follow browser CORS rules.
225
246
 
226
- Supported options are `autoplay`, `controls`, `speed`, `theme`, `reduced-motion`, and `hover-fill`. Hover fill is off by default; set `hover-fill` to enable background and content feedback. Use `controls="false"` to hide the controls, which are visible by default. Autoplay is off by default. Option changes preserve the current time and playing/paused state. Removing the element cleans up the player and pending requests; reconnecting can mount it again from its retained document.
247
+ Attributes: `autoplay`, `controls`, `speed`, `theme`, `reduced-motion`, and `hover-fill`. Controls default on; autoplay and hover fill default off. Use `controls="false"` to hide controls. Changes to theme, controls, or motion/hover options preserve time and playback state; `autoplay` explicitly starts or pauses playback. Removal cancels requests and destroys the player; reconnection uses the retained document.
227
248
 
228
- The element exposes `play`, `pause`, `seek`, `step`, `setSpeed`, `getState`, and `destroy`. Replace the document through the `document` property. Before mounting, `getState()` returns `undefined`. The element forwards the four playback/error event types as bubbling, composed CustomEvents that can cross Shadow DOM boundaries.
249
+ Methods: `play`, `pause`, `seek`, `step`, `setSpeed`, `getState`, and `destroy`. Update documents through `element.document`. Before mount or after destruction, `getState()` returns `undefined`. Playback/error events bubble across Shadow DOM boundaries.
229
250
 
230
- Load this entry in a DOM environment for actual registration. Importing without a DOM is safe, but module evaluation fixes the HTMLElement base class; the same module instance imported without a DOM cannot register directly after a DOM is added later.
251
+ Register in a DOM environment. DOM-free imports are safe, but that module instance cannot register after a DOM is added later.
231
252
 
232
253
  ## Documents and timelines
233
254
 
@@ -240,17 +261,19 @@ Load this entry in a DOM environment for actual registration. Importing without
240
261
  | `scenes` | `[]` creates a static diagram; each scene has a unique `id`, a `label`, and nonempty `beats` |
241
262
  | `title` / `theme` | Optional title and `light` / `dark` document theme |
242
263
 
243
- Node and edge IDs are globally unique. Node shapes are `rect`, `roundedRect`, `ellipse`, `cylinder`, and `group`. A node's `parentId` references its parent group; child coordinates are relative to the group origin. Labels are plain text; `visible` defaults to `true`. Node `style` supports `fill`, `stroke`, `strokeWidth`, `textColor`, and `dashed` (used for group borders); edge `style` supports `stroke`, `strokeWidth`, and `dashed`. Colors use hexadecimal RGB/RGBA, such as `#2563eb` or `#2563eb80`.
264
+ Node and edge IDs are globally unique. Shapes: `rect`, `roundedRect`, `ellipse`, `cylinder`, `group`. `parentId` names a parent group; child coordinates are group-relative. Labels are plain text; `visible` defaults to `true`. Node styles: `fill`, `stroke`, `strokeWidth`, `textColor`, `dashed` (group borders). Edge styles: `stroke`, `strokeWidth`, `dashed`. Colors use hexadecimal RGB/RGBA, such as `#2563eb` or `#2563eb80`.
244
265
 
245
- Edge `route` is `straight` by default, `orthogonal`, or `curved`. Curved routes are smooth cubic paths generated from endpoint ports; they do not auto-avoid other nodes. Orthogonal paths avoid visible ordinary nodes, with a deterministic fallback when no route is found; shortest paths and zero crossings are not guaranteed. Use `fromPort` and `toPort` (`top`, `right`, `bottom`, or `left`) when a specific side is required. Explicit orthogonal ports reserve outward endpoint segments and route around endpoint bodies; curved ports set endpoint tangents. Omitted ports are selected from the dominant center-to-center axis, and ellipse ports use the ellipse contour. Simple labels wrap using deterministic width estimates. Insufficient node height preserves the full label and emits a `text-overflow` warning.
266
+ Routes: `straight` (default), `orthogonal`, or `curved`. Curves follow endpoint ports without avoiding other nodes. Orthogonal routes avoid visible ordinary nodes and use a fixed fallback if blocked; shortest paths and zero crossings are not guaranteed. Set `fromPort` / `toPort` to `top`, `right`, `bottom`, or `left`. Explicit orthogonal ports reserve outward endpoint segments; curved ports set tangents. Omitted ports follow the dominant center-to-center axis; ellipse endpoints lie on the contour. Simple labels wrap by estimated width and remain visible on height overflow, with a `text-overflow` warning.
246
267
 
247
- Edges may connect ordinary nodes, groups, or one of each. A group endpoint uses the evaluated group rectangle, including nested movement and explicit ports. Hiding an endpoint group or its ancestor masks the edge. A transfer highlights the endpoint group itself; descendants keep their own highlight state. Group frames remain excluded from routing obstacles.
268
+ Edges can connect nodes or group boundaries. Group endpoints follow evaluated geometry and explicit ports. Hidden ancestors mask connected edges. Transfers highlight the endpoint group, not its descendants. Group frames are excluded from routing obstacles.
248
269
 
249
270
  ### Fixed-size node content
250
271
 
251
- A `rect`, `roundedRect`, or `cylinder` node may declare `content` instead of `label`. Ellipses and groups accept simple labels only. Declaring both fields, including an empty label, is invalid. `content.blocks` is an ordered array of `title`, `paragraph`, `list` and `graph` blocks. Paragraph `role` is `body` (default) or `auxiliary`. An empty block array displays only the shape. Import the `DiagramNode` type from `flowsvg` for this example:
272
+ `rect`, `roundedRect`, and `cylinder` support `content`; ellipses and groups support labels only. `content` and `label` are mutually exclusive, even with an empty label. `content.blocks` contains ordered `title`, `paragraph`, `list`, or `graph` blocks. Paragraph roles are `body` (default) and `auxiliary`. An empty block array displays only the shape.
252
273
 
253
274
  ```ts
275
+ import type { DiagramNode } from "flowsvg";
276
+
254
277
  const service = {
255
278
  id: "service", kind: "rect", x: 24, y: 24, width: 260, height: 312,
256
279
  content: {
@@ -265,40 +288,55 @@ const service = {
265
288
  } satisfies DiagramNode;
266
289
  ```
267
290
 
268
- Title/paragraph `text` and list-item `body`/`auxiliary`/`status`/tag `text` accept a string or `ContentTextSpan[]`. Each span is `{ text, style?: { fontSize, fontWeight, color } }`; alignment stays on the block. For example: `text: [{ text: "Saved", style: { fontWeight: 700 } }, { text: " 2026-10-17", style: { fontSize: 12 } }]`. Spans inherit block defaults and share each line's baseline. Line height uses that line's largest font, and style boundaries do not force line breaks. Wrapping prefers English spaces, then breaks CJK, long words and URLs between graphemes. `Intl.Segmenter` preserves combining accents, skin-tone emoji, ZWJ sequences, flags and keycaps. A grapheme crossing source spans uses its first character's style. Layout does not read DOM measurements or font-loading state.
291
+ The `service` node rendered with the light theme:
269
292
 
270
- Each block accepts `style.fontSize` (finite and positive), `fontWeight` (integer 100–900), hexadecimal `color`, and `align` (`left`, `center`, `right`). Content style accepts nonnegative finite `padding` and `blockGap`. Sizes and spacing use diagram units. Defaults are title 20/700, body 14/400, auxiliary 12/400, padding 16, block gap 14, and line height at least 20 or font size × 1.35. Auxiliary text uses a muted theme color; explicit content colors retain their authored values.
293
+ ![Fixed-size Order service node with title, auxiliary text, body and a blue right-aligned paragraph](https://cdn.jsdelivr.net/npm/flowsvg@0.1.1/docs/images/node-content.png)
271
294
 
272
- A list is `{ type: "list", marker?: "none" | "bullet" | "number", items: ContentListItem[], style?: ContentListStyle }`. The default marker is `none`. Each item accepts optional `tags`, `status`, `body`, `auxiliary`, body `style`, `auxiliaryStyle`, and `statusStyle`. List `style` supplies body typography and a nonnegative finite `itemGap` (default 12). Item body styles override the list's body styles; auxiliary text keeps its muted 12-unit defaults unless `auxiliaryStyle` overrides them. Marked lists reserve one common marker column, including multi-digit numbers, then an 8-unit gap. Body continuation lines and auxiliary text use the same body column. Markers align with the first present text part's baseline. Absent parts and completely absent entries consume no height or item gap; explicit empty strings and blank lines still consume authored lines. For example:
295
+ Text fields accept strings or `ContentTextSpan[]`: `{ text, style?: { fontSize, fontWeight, color } }`. Spans inherit block styles and share a baseline; alignment belongs to the block. Each line uses its largest font. Wrapping preserves newlines and Unicode graphemes, prefers English spaces, and breaks long words/URLs as needed. A grapheme spanning source styles uses its first character’s style. Layout uses width estimates, without DOM measurement.
296
+
297
+ Text styles: positive finite `fontSize`, integer `fontWeight` from 100–900, hexadecimal `color`, and `align` (`left`, `center`, `right`). Content `padding` and `blockGap` are finite and nonnegative. Defaults in diagram units: title 20/700, body 14/400, auxiliary 12/400, padding 16, block gap 14. Line height is `max(20, fontSize × 1.35)`. Explicit colors override theme defaults.
298
+
299
+ Lists accept `marker` (`none` by default, `bullet`, or `number`), `items`, and `style`. Items support `tags`, `status`, `body`, `auxiliary`, `style`, `auxiliaryStyle`, and `statusStyle`. List style sets body typography and nonnegative finite `itemGap` (default 12); item body style overrides it. Auxiliary text defaults to muted 12-unit text. Markers share a column followed by an 8-unit gap; continuation and auxiliary lines align with the body. Markers use the first present text baseline. Omitted items/parts take no space; explicit blank text preserves a line.
273
300
 
274
301
  ```ts
275
- { type: "list", marker: "number", style: { itemGap: 16 }, items: [
302
+ import type { ContentBlock } from "flowsvg";
303
+
304
+ const steps: ContentBlock = { type: "list", marker: "number", style: { itemGap: 16 }, items: [
276
305
  { body: "Validate the request.", auxiliary: "Input checks" },
277
306
  { body: [{ text: "Save", style: { fontWeight: 700 } }, { text: " the order." }] }
278
- ] }
307
+ ] };
279
308
  ```
280
309
 
281
- Tags use `{ text: ContentText, style?: { fontSize, fontWeight, color, fill } }`. Tag font size defaults to 11; each background reserves 6 units on each horizontal side and 2 on each vertical side, with a 3-unit corner radius. List `style.tagGap` is finite and nonnegative (default 6), separating tags, tag rows and same-row status. Tags use theme background/text colors unless explicitly overridden. Status defaults to 12-unit theme accent text; `statusStyle` accepts only `fontSize`, `fontWeight`, and `color`, and status remains right-aligned. A single tag row shares a baseline with a single-line status when their natural widths fit. Otherwise tags wrap first and status starts below all tags. Long tags and status use the same mixed-style grapheme wrapping. Body follows the metadata row(s), then auxiliary text. Omitted parts reserve no space. For example:
310
+ The `steps` block rendered in a 300 × 180 rectangle with `content: { blocks: [steps] }`:
311
+
312
+ ![Two numbered steps with auxiliary text and bold Save text](https://cdn.jsdelivr.net/npm/flowsvg@0.1.1/docs/images/numbered-list.png)
313
+
314
+ Tags accept `{ text, style?: { fontSize, fontWeight, color, fill } }`. Defaults: font size 11, padding 6 horizontal / 2 vertical, corner radius 3. List `tagGap` defaults to 6 and must be finite and nonnegative. Status is right-aligned, defaults to 12-unit accent text, and accepts `fontSize`, `fontWeight`, and `color` in `statusStyle`. Tags and status share a baseline when they fit; otherwise tags wrap first and status moves below. Body and auxiliary text follow. Explicit colors/fills override the theme.
282
315
 
283
316
  ```ts
284
- { type: "list", items: [{
317
+ import type { ContentBlock } from "flowsvg";
318
+
319
+ const records: ContentBlock = { type: "list", items: [{
285
320
  tags: [{ text: "world" }, { text: "experience", style: { fill: "#eadfff", color: "#57319b" } }],
286
321
  status: "new", body: "Alice joined Google", auxiliary: "Mar 2026"
287
- }] }
322
+ }] };
288
323
  ```
289
324
 
290
- Node dimensions stay fixed. Rectangles apply the authored padding. Rounded rectangles reserve an additional 4 units on each side before padding. Cylinders reserve twice the current cap radius above and below content before padding; the cap radius is `min(14, height / 4)`. The safe area has nonnegative dimensions, including when shape clearance and padding consume the entire node. Content starts at the top of its safe area and wraps using deterministic width estimates. Explicit line endings and blank lines are preserved. SVG clips text to the safe area. Insufficient width or height emits a separate `text-overflow` warning with the node ID, `nodes[index].content` path, and direction. Empty safe areas remain valid. Clipped text does not extend node hit bounds. Simple labels retain their existing overflow behavior.
325
+ The `records` block rendered in a 300 × 180 rectangle with `content: { blocks: [records] }`:
291
326
 
292
- `EvaluatedNode.contentLayout` exposes `safeArea`, ordered `lines`, tag backgrounds `tags`, `widthOverflow`, and `heightOverflow`. Each line provides text, baseline coordinates, estimated width, typography, alignment, semantic role (`title`, `body`, `auxiliary`, `marker`, `tag` or `status`), source `blockIndex`, and optional list `itemIndex`. Tag lines also expose `tagIndex`. Each tag background provides absolute `x`, `y`, `width`, `height`, resolved `fill`, author provenance `explicitFill`, and source `blockIndex`/`itemIndex`/`tagIndex`. Each line's `spans` provide text, absolute left coordinates, shared baseline, estimated width, and resolved typography/color. `explicitColor` records whether the block or source span supplied its color. The same values drive browser SVG and static export. Content follows current absolute geometry, including interpolated dimensions and parent-group movement, ancestor visibility, and effective opacity. Content does not change ports, edge routes, transfer geometry, or node-parent rules. See the runnable [title and paragraph example](https://github.com/yokingma/FlowSVG/blob/HEAD/apps/native-example/node-content.html) and [list example](https://github.com/yokingma/FlowSVG/blob/HEAD/apps/native-example/list-content.html).
327
+ ![Record with world and experience tags, new status, body and date](https://cdn.jsdelivr.net/npm/flowsvg@0.1.1/docs/images/record-tags.png)
293
328
 
294
- All three content shapes support initial content, styled spans, lists and timeline replacement. Tags, status and readable default colors under explicit, flow and hover highlights are supported.
329
+ Content keeps authored node dimensions. Safe areas reserve padding plus 4 units per side for rounded rectangles, or twice the cylinder cap radius above/below (`min(14, height / 4)`). Dimensions clamp to zero. SVG clips content to this area; width/height overflow emits `text-overflow` with node ID, `nodes[index].content`, and direction. Clipped text adds no hit area. Simple labels retain their separate overflow behavior.
295
330
 
331
+ `EvaluatedNode.contentLayout` provides the safe area, lines/spans, tag backgrounds, optional graph primitives, and width/height overflow flags. Geometry is absolute and follows node resizing, parent movement, visibility, and opacity. Lines retain source block/item/tag indexes; `explicitColor` and `explicitFill` identify authored paint. Browser rendering and SVG export use the same layout. Content does not change ports, routes, transfer geometry, or parent relationships. See the [paragraph](https://github.com/yokingma/FlowSVG/blob/HEAD/apps/native-example/node-content.html) and [list](https://github.com/yokingma/FlowSVG/blob/HEAD/apps/native-example/list-content.html) examples.
296
332
 
297
333
  ### Entity graphs inside nodes
298
334
 
299
- A `graph` block draws a small, undirected entity graph inside any supported content shape. Authors specify local coordinates; the SDK does not arrange entities automatically. Use `ContentGraphBlock` from `flowsvg`:
335
+ A `graph` block draws an undirected entity graph with authored local coordinates:
300
336
 
301
337
  ```ts
338
+ import type { ContentGraphBlock } from "flowsvg";
339
+
302
340
  const entities: ContentGraphBlock = {
303
341
  type: "graph", height: 130,
304
342
  nodes: [
@@ -310,13 +348,17 @@ const entities: ContentGraphBlock = {
310
348
  };
311
349
  ```
312
350
 
313
- `height` is positive and reserves a fixed vertical section, followed by the ordinary block gap. Entity centers `x`/`y` are nonnegative finite offsets from the section's top-left corner in diagram units. IDs must be unique within each graph block; each relation references two local IDs. Labels use escaped plain text at 12 units, centered above 4-unit dots. Selected entities use accent dots and bold labels; a relation uses accent paint when both endpoints are selected. Text and primitives share the parent content clip and theme/highlight paint. Coordinates or labels outside the graph section produce directional overflow warnings without moving the following block. Leave enough room above each dot for its label. Entities remain decorative: clicking or hovering them targets the parent node.
351
+ The `entities` block rendered in a 280 × 180 rectangle with `content: { blocks: [entities] }`:
352
+
353
+ ![Entity graph with highlighted Alice and Google, and an unselected ML project](https://cdn.jsdelivr.net/npm/flowsvg@0.1.1/docs/images/entity-graph.png)
314
354
 
315
- `contentLayout.graphs`, when present, provides absolute circle/line geometry, local `highlighted` state and resolved colors per `blockIndex`. Graph labels join `contentLayout.lines`. Replace the complete `content` at a beat start to change local selections. The [complete Hindsight example](https://github.com/yokingma/FlowSVG/blob/HEAD/apps/native-example/hindsight.html) demonstrates all three acts and 25 beats, including group endpoints and changing entity selections.
355
+ `height` reserves a positive fixed section. Entity centers use nonnegative finite `x`/`y` offsets from its top-left. IDs are local to each block; edges reference those IDs. Labels are 12-unit plain text above radius-4 dots. Highlighted entities use accent dots and bold labels; an edge uses accent paint when both endpoints are highlighted. Overflow reports its direction without shifting later blocks. Graphs share the parent clip and theme; interactions target the parent node.
356
+
357
+ `contentLayout.graphs` contains absolute primitives and highlight state by `blockIndex`; labels join `contentLayout.lines`. Replace `content` to change selections at a beat start. See the [Hindsight example](https://github.com/yokingma/FlowSVG/blob/HEAD/apps/native-example/hindsight.html).
316
358
 
317
359
  ### Author a complete Facts timeline
318
360
 
319
- Start with fixed shape dimensions, then choose ordered blocks. Use `marker: "none"` for records, `"bullet"` for capabilities, and `"number"` for steps. Give each entry only the parts it needs. Dates are plain text; a small inline span keeps a date on the body baseline. This complete document can be passed unchanged to any host:
361
+ Choose fixed dimensions and ordered blocks. Use unmarked lists for records, bullets for capabilities, and numbers for steps. This document works with all four integrations:
320
362
 
321
363
  ```ts
322
364
  import { createDiagram, type DiagramDocument, type NodeContent } from "flowsvg";
@@ -351,9 +393,13 @@ console.log(diagram.evaluateAt(1000).warnings);
351
393
  const svg = diagram.renderSvg(1000, { theme: "dark", reducedMotion: true });
352
394
  ```
353
395
 
354
- Review `contentLayout.safeArea` and `frame.warnings` before sharing a document. If content exceeds the safe area, increase the authored dimensions or shorten the text. The SDK clips overflow and never resizes nodes or shrinks fonts. Prefer default role colors when switching themes or highlighting; explicit colors remain the author's responsibility. Content is one node interaction target, with no internal links, buttons, images, HTML, or independent timeline targets.
396
+ The initial light-theme frame at 0 ms and the updated dark-theme frame at 1000 ms:
397
+
398
+ ![Facts timeline before and after record replacement, shown in light and dark themes](https://cdn.jsdelivr.net/npm/flowsvg@0.1.1/docs/images/facts-timeline.png)
355
399
 
356
- The [official content story](https://github.com/yokingma/FlowSVG/blob/HEAD/apps/native-example/content-story.html) combines Facts, service capabilities and numbered steps with nested group movement, node resizing, explicit ports, all three edge routes, synchronized flows and focus. Its [shared document](https://github.com/yokingma/FlowSVG/blob/HEAD/apps/native-example/content-story.js) is also used by the React and Vue example applications. Native and Web Component hosts appear together. Normal, narrow and long-text links expose fixed-area clipping; theme and reduced-motion controls apply the same visual options. All hosts use the built-in play/pause, speed, step, scene and progress controls.
400
+ Check `frame.warnings` before sharing. Increase node dimensions or shorten overflowing text; the SDK does not resize nodes or shrink fonts. Prefer default colors for theme/highlight contrast. Content has one node-level interaction target, without internal links, buttons, images, or HTML.
401
+
402
+ The [content story](https://github.com/yokingma/FlowSVG/blob/HEAD/apps/native-example/content-story.html) combines Facts, lists, node updates, flows, and focus. Its [shared document](https://github.com/yokingma/FlowSVG/blob/HEAD/apps/native-example/content-story.js) also runs in the Vue and React examples.
357
403
 
358
404
  Each beat has a positive `durationMs` and an `actions` array, with optional `transitionMs` and `caption`. Transition duration defaults to `min(250, durationMs)` and must be within `[0, durationMs]`. An omitted caption inherits the previous caption; an empty string clears it.
359
405
 
@@ -366,7 +412,7 @@ Each beat has a positive `durationMs` and an `actions` array, with optional `tra
366
412
  | `updateEdge` | `value` supports `label`, `stroke`, `strokeWidth`, and `opacity` |
367
413
  | `focus` | `target` is a node/group ID; `null` restores the full viewport |
368
414
 
369
- `updateNode.value.content` replaces the complete content object at the beat start, including when `transitionMs` is nonzero. The result persists into later beats and scenes. A `label` update switches to simple-label mode; a content update switches to structured-content mode. `content: null` clears either text mode and leaves only the shape. Clearing never restores an earlier label or changes node dimensions. Do not assign both `label` and `content`, or assign either mode more than once in the same beat; these declarations produce field-path errors. Seeking, pausing, playback speed, and reduced motion use the same content time boundary.
415
+ `updateNode.value.content` replaces all content at beat start, regardless of transition duration, and persists across beats/scenes. `label` switches to simple-label mode; `content` switches to structured content. `content: null` clears either mode without restoring old labels or resizing. Assigning both modes, or assigning one repeatedly within a beat, produces a field-path error.
370
416
 
371
417
  ```ts
372
418
  // Each value belongs to a separate beat's updateNode action.
@@ -377,13 +423,13 @@ const simpleLabel: NodeVisualState = { label: "Summary" };
377
423
  const clearText: NodeVisualState = { content: null };
378
424
  ```
379
425
 
380
- All scenes share a global timeline in milliseconds. Each scene inherits the previous scene's final state. Labels and captions change at beat start; interpolated properties change during the transition. Beat intervals include their start and exclude their end; the final time returns the completed final frame. Hiding a node or group also masks connected edges; showing it again restores visibility according to each object's saved state.
426
+ Scenes share a millisecond timeline and inherit the previous scene’s final state. Text changes at beat start; interpolated values change during the transition. Beats include their start and exclude their end; the final time returns the completed frame. Hidden nodes/groups mask connected edges; showing them restores each edge’s saved visibility.
381
427
 
382
- A beat can transfer along several distinct edges at once. All transfers start at the beat start, use the same linear completion ratio over `durationMs`, and disappear at the beat end. Different path lengths therefore produce different physical speeds. Shared endpoint nodes remain highlighted while any current transfer uses them. The next beat replaces the complete transfer group, including across scene boundaries. Transfer highlights do not inherit; explicit highlights and ordinary styles retain their existing inheritance rules.
428
+ A beat supports one transfer per distinct edge. Transfers share linear progress over `durationMs`, so longer paths move faster. Markers and endpoint highlights expire at beat end; the next beat supplies a new transfer group. Explicit highlights and ordinary styles persist.
383
429
 
384
- ## API
430
+ Transfer `text` is escaped, stays horizontal, and uses estimated capsule widths without font measurement. It does not replace the edge label or create an interaction target. An empty string still produces a capsule.
385
431
 
386
- Transfer text belongs to one action and does not replace the edge's static `label`. Capsule width uses deterministic Unicode code-point estimates with padding; it does not use browser font measurements. Text remains horizontal on every route and is escaped as plain text. Capsules are decorative, with no independent pointer or keyboard interaction. A provided empty string still uses a capsule.
432
+ ## API
387
433
 
388
434
  ### Core API
389
435
 
@@ -400,11 +446,11 @@ Import from `flowsvg`:
400
446
  | `diagram.hitTest(timeMs, point, options?)` | Returns `{ id, type: "node" \| "edge" }` or `null`, using document coordinates |
401
447
  | `wrapText(label, maxWidth, typography?)` | Wraps text with fixed width estimates and returns an array of lines |
402
448
 
403
- `EvaluateOptions` is `{ theme?: "light" | "dark"; reducedMotion?: boolean }`. Finite times are clamped to `[0, totalDuration]`; non-finite times become zero. Hit testing ignores hidden or zero-opacity objects and checks ordinary nodes, then edges, then group backgrounds. Group hover highlights only the border and preserves the evaluated fill, including any active timeline highlight.
449
+ `EvaluateOptions`: `{ theme?: "light" | "dark"; reducedMotion?: boolean }`. Times clamp to `[0, totalDuration]`; non-finite values become zero. Hit testing skips hidden/zero-opacity objects, then checks ordinary nodes, edges, and group backgrounds in that order.
404
450
 
405
- `DiagramFrameState` includes `timeMs`, `sceneId`, `beatId`, `beatProgress`, `nodes`, `edges`, `camera`, optional `caption`, and `warnings`. An active `EvaluatedEdge` may include `flow` with its direction, linear beat progress, document-coordinate point, and optional message `text`; the marker is omitted at the beat end, when the edge is hidden, or when reduced motion is enabled. Evaluated node coordinates are absolute document coordinates. Scene and beat IDs are `null` for static documents. Beat IDs use `${scene.id}:${beatIndex}`, with zero-based indexes.
451
+ `DiagramFrameState` includes time, scene/beat IDs, progress, evaluated nodes/edges, camera, caption, and warnings. Coordinates are absolute. Static documents have `null` scene/beat IDs; beat IDs use `${scene.id}:${beatIndex}` with zero-based indexes. Active edges expose `flow` direction, progress, point, and optional text. Markers are absent after the beat, on hidden edges, or with reduced motion.
406
452
 
407
- Main public types include `DiagramDocument`, `DiagramNode`, `DiagramEdge`, `Scene`, `Beat`, `Action`, `Diagram`, `DiagramFrameState`, `EvaluateOptions`, `ValidationIssue`, and `LayoutWarning`. Type declarations and JSDoc for key members are included in the package.
453
+ Types and member JSDoc are included: `DiagramDocument`, `DiagramNode`, `DiagramEdge`, `Scene`, `Beat`, `Action`, `Diagram`, `DiagramFrameState`, `EvaluateOptions`, `ValidationIssue`, and `LayoutWarning`.
408
454
 
409
455
  ### Player API
410
456
 
@@ -421,13 +467,13 @@ Import `createPlayer(host, document, options?)` from `flowsvg/browser`. `PlayerO
421
467
  | `getState()` | Returns `PlayerState`: `timeMs`, `totalDuration`, `playing`, `speed`, and `frame` |
422
468
  | `destroy()` | Idempotently cleans up DOM, listeners, and scheduling; stops events, while state remains readable |
423
469
 
424
- Players start paused at zero, with built-in controls enabled. Hiding the page pauses playback; making it visible again leaves playback paused. Playback stops at the final frame. Static diagrams disable timeline controls. Framework handles provide the control and state methods above, excluding `element` and `destroy`; frameworks manage the host and cleanup.
470
+ Players start paused at zero with controls enabled. Page hiding pauses playback; returning leaves it paused. Playback stops at the end; static diagrams disable timeline controls. Framework handles expose these methods except `element` and `destroy`.
425
471
 
426
- Hovering a visible node's shape, simple label, or structured content highlights that node's border during playback or pause. The default border-only feedback preserves the evaluated fill and content paint. Set `hoverFill: true` to also paint the background and structured content. Hover does not highlight neighboring nodes or edges. Leaving the node removes only the hover cause; active flow and explicit highlights remain. After all causes end, the current ordinary style is shown. The player rechecks a stationary pointer after frame updates and clears hover on pointer cancellation, successful document replacement, or destruction.
472
+ Hover highlights the current node’s border, including hits on labels or content. Set `hoverFill: true` to include background and content paint, including groups. Leaving removes only the hover highlight; active flow/explicit highlights remain. Frame updates recheck stationary pointers. Pointer cancellation, successful document replacement, and destruction clear hover.
427
473
 
428
- Default title/body/marker, auxiliary, tag, and status paint stays readable in both themes and during explicit, flow, and hover highlights. Intermediate highlight frames select contrasting default ink against the current evaluated fill. Authored span/block colors and tag fills remain unchanged. Highlight paint never changes text metrics, safe areas, or hit bounds. Content children have no separate IDs, keyboard focus, links, or buttons; content clicks report the node. Clipped text adds no hit area. Content replacement, text-mode changes, and node geometry updates reapply hover to the current frame; successful load clears the old document's hover.
474
+ Default content colors adapt to the theme and highlight fill; authored colors and tag fills stay unchanged. Highlighting preserves layout and hit bounds. Content interactions report the parent node; clipped text adds no hit area.
429
475
 
430
- `PlayerState.frame` remains the core frame and excludes hover. Static SVG exports also exclude hover; browser output matches the same-time export when no node is hovered.
476
+ `PlayerState.frame` and static SVG exclude hover. Browser output matches the same-time export when no node is hovered.
431
477
 
432
478
  ### Events
433
479
 
@@ -441,9 +487,9 @@ Native players dispatch `CustomEvent` on the host element:
441
487
  | `error` | `PlayerErrorDetail`: retained player state and `error` |
442
488
  | `hover` / `click` | `PlayerInteractionDetail` or `null`; aliases are `objecthover` / `objectclick` |
443
489
 
444
- Interaction details contain `id`, `type`, document-coordinate `point`, `timeMs`, `sceneId`, and `beatId`; a miss produces `null`. Vue, React, and Web Component wrappers forward the first four event types. Web Component errors before mounting may lack player state.
490
+ Interaction detail contains `id`, `type`, `point` (document coordinates), `timeMs`, `sceneId`, and `beatId`; misses return `null`. Adapters forward the four playback/error events. Web Component errors before mount may lack player state.
445
491
 
446
- Pointer movement emits `hover`; frame updates also emit it when the object under a stationary pointer changes. Leaving or cancelling the pointer emits `null`.
492
+ `hover` also updates when a frame change moves another object under a stationary pointer. Pointer leave/cancel emits `null`.
447
493
 
448
494
  ```ts
449
495
  import type { PlayerEventMap } from "flowsvg/browser";
@@ -460,7 +506,19 @@ This example reuses `player` from the native integration. Remove any listeners y
460
506
 
461
507
  ## Themes and accessibility
462
508
 
463
- Theme precedence is explicit option → document theme → light. The core does not read the system theme. Core `reducedMotion` defaults to off; the browser follows `prefers-reduced-motion` and responds to changes unless an explicit option is supplied. Reduced motion completes transitions immediately while preserving duration and time position.
509
+ Theme: explicit option → document theme → light. The core ignores system preferences. Browser motion follows `prefers-reduced-motion` unless overridden; the core defaults to full motion. Reduced motion completes transitions immediately and hides transfer markers, preserving duration.
510
+
511
+ Defaults use neutral borders and edges with blue emphasis:
512
+
513
+ | Paint | Light | Dark |
514
+ | --- | --- | --- |
515
+ | Background / node fill | `#ffffff` / `#ffffff` | `#101828` / `#1d2939` |
516
+ | Node border / text | `#cfd8e3` / `#1d2939` | `#475467` / `#f2f4f7` |
517
+ | Edge | `#8892a4` | `#98a2b3` |
518
+ | Highlight fill / border | `#eaf1ff` / `#2a63c8` | `#233b60` / `#8ab4f8` |
519
+ | Flow marker / marker text | `#2a63c8` / `#ffffff` | `#8ab4f8` / `#101828` |
520
+
521
+ Default node borders are 1.2 diagram units; edges are 2. Rounded rectangles use a radius of at most 8. Node labels use plain text; edge labels retain a background halo for readability across paths. Diagram text uses system fonts with Chinese fallbacks. Explicit styles replace ordinary defaults; timeline and hover highlights overlay those styles and restore the current ordinary paint when removed.
464
522
 
465
523
  Import `flowsvg/styles.css` for control layout and focus styles. CSS variables customize controls only; core themes and document styles determine diagram colors:
466
524
 
@@ -474,52 +532,16 @@ Import `flowsvg/styles.css` for control layout and focus styles. CSS variables c
474
532
  }
475
533
  ```
476
534
 
477
- Add the `flowsvg` class to a native host to use this rule; framework components add it automatically. With the canvas focused, Space plays/pauses and the left/right arrow keys step through beats. Form controls retain their native key behavior. Captions use a polite live region, and each SVG has its own accessible name.
535
+ Add `flowsvg` to a native host to apply these variables; framework components add it automatically. With canvas focus, Space toggles playback and arrow keys step through beats. Form controls keep native keys. Captions use a polite live region; SVGs have accessible names.
478
536
 
479
537
  ## Validation and errors
480
538
 
481
- Loading rejects unknown fields, invalid references, cyclic groups, duplicate assignments within a beat, invalid colors, and invalid dimensions/durations. Text is escaped as plain text. To check external JSON first, call `validateDocument` and display each issue's `path` and `message`.
539
+ Validation rejects unknown fields, bad references, cyclic groups, duplicate beat assignments, invalid colors, and invalid dimensions/durations. Text is escaped. For external JSON, call `validateDocument` and inspect each issue’s `path` and `message`.
482
540
 
483
- `createDiagram` and `createPlayer` with an invalid initial document throw `DiagramValidationError`, whose `issues` contain field paths. An existing player's `load` reports validation failure through the `error` event and preserves the old state. Text height overflow is a nonblocking layout warning; inspect `frame.warnings` to decide whether to resize nodes.
484
-
485
- Each beat permits at most one `flow` per edge, even when directions or text differ. Repeating an edge reports `duplicate beat assignment` at the later action’s `target`, independently of other field errors. A flow can coexist with visibility, explicit highlight, and ordinary style actions on the same edge; those actions retain their own validation rules.
486
-
487
- ## Development and verification
488
-
489
- Run these commands from the source repository root. Development uses Node.js 22.12+ and pnpm 10.12.1; Vue/React examples use Vite 7.
490
-
491
- | Command | Purpose |
492
- | --- | --- |
493
- | `pnpm install` | Install workspace dependencies |
494
- | `pnpm build` | Build SDK JavaScript, declarations, and styles |
495
- | `pnpm typecheck` | Check core, browser, and framework entry types |
496
- | `pnpm test` | Run Vitest behavior tests |
497
- | `pnpm --filter flowsvg-vue-example dev` | Start the Vue example |
498
- | `pnpm --filter flowsvg-react-example dev` | Start the React example |
499
- | `pnpm release:verify` | Pack from a missing `dist`, verify the file allowlist and every export, then check framework-free imports, strict consumer types and all four host lifecycles |
500
- | `pnpm release` | Run all release gates and npm preview; retain the verified tarball in `dist/releases/` without uploading |
501
- | `pnpm release:publish` | Check npm authentication, repeat all release gates and preview, then upload the verified tarball |
541
+ `createDiagram` and initial `createPlayer` calls throw `DiagramValidationError` for invalid documents. Its `issues` contain field paths. Later `load` failures emit `error` and retain the old state. Text overflow is nonblocking; inspect `frame.warnings`.
502
542
 
503
- Maintainers: follow the [npm release guide](https://github.com/yokingma/FlowSVG/blob/HEAD/docs/RELEASING.md). Run SDK packaging before example builds; packaging removes and rebuilds `dist`.
504
-
505
- After building, run the Node.js static server from the repository root:
506
-
507
- ```sh
508
- pnpm dlx http-server@14.1.1 . -p 4175 -c-1
509
- ```
510
-
511
- The first run downloads the CLI without adding a project dependency. Open <http://localhost:4175/apps/native-example/> for the native example. For browser acceptance checks, open the `/packages/flowsvg/tests/browser-*.html` pages: `controls`, `lifecycle`, `visual-updates`, `focus`, `accessibility`, `web-component`, `flow-text`, and `parallel-flows`. Inspect the corresponding `window.browserTestResults`, `window.browserFocusResults`, `window.browserAccessibilityResults`, `window.browserWebComponentResults`, or `window.browserFlowTextResults`; every result's `passed` must be `true`.
512
-
513
- Open `/packages/flowsvg/tests/browser-hover.html` for real-pointer hover checks. Hover the source shape and overflowing label, use the flow/after-flow controls, and verify that leaving preserves active flow highlights and restores the latest ordinary style afterward. Check stationary-pointer updates, reload cleanup, and destroy cleanup with the remaining controls.
514
-
515
- To verify automatic reduced motion, open the accessibility page, change the browser's reduced-motion media emulation, then call `window.verifyMotionPreference(true)` or `false`. Check that preference changes match SVG export with the same options.
516
-
517
- Reuse compiled diagrams during playback and avoid recreating players on every render. Measure frame costs for large diagrams on target devices. See [CHANGELOG](CHANGELOG.md) for changes and [PATTERNS](PATTERNS.md) for implementation conventions.
518
-
519
- Parallel transfer checks and a playable three-then-two transfer demo are in `tests/browser-parallel-flows.html`. The checks compare public frames and SVG output across scene boundaries, themes, and reduced motion.
543
+ Duplicate `flow` actions on one edge report `duplicate beat assignment` at the later `target`, regardless of direction, text, or other field errors. Flows may coexist with visibility, explicit highlight, and style actions.
520
544
 
521
545
  ## License
522
546
 
523
547
  [MIT](LICENSE). Use, modify, and distribute with the license and copyright notice retained.
524
-
525
- Open [the node-content pointer fixture](https://github.com/yokingma/FlowSVG/blob/HEAD/packages/flowsvg/tests/browser-node-content.html) (add `?theme=dark` for dark mode) to check all three shapes, tags, clipped long text, explicit/flow/hover layering, stationary-pointer content/text-mode/size updates, theme replacement and destruction.