Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .changes/playback-gate-path.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "type": "user-facing", "releaseNoteId": "release:0.22.0" }
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@ All notable Loop Studio releases, newest first. Behavioral changes are pinned
in versioned spec documents (see the [README](README.md#technical-reference));
this file is the narrative history, not the contract.

## v0.22.0 — 2026-10-09

Playback shows what moved where (issue #330, first of three): the path a Gate took, every amount as a `+N` badge beside its round token, and Focus mode winning on connections, as [`docs/simulation-playback.md`](docs/simulation-playback.md) §PB4.6 and [`docs/large-graph-readability.md`](docs/large-graph-readability.md) §LGR2.3 describe.

- **The Gate path.** For every Gate, deterministic or probabilistic, each outgoing connection that carried a move this step is highlighted under its round token, from its onset to settle; several can be at once. A branch with no move keeps its usual look, with no faded or crossed-out cue.
- **`+N` beside the round token.** The round token still travels wherever it did; the step's summed amount now always rides beside it as a `+N` badge, from `+1` (before, a bare number sat above the dot only when the amount was above 1). The badge is drawn above every connection label. At the map zoom level the badge is left out with the dot, as before.
- **The connection's own label stays put.** It dims only while the token or its badge covers its real box; no other connection's label is tested.
- **24 token-and-badge pairs per step** replace the budget of 60 travelling cues. Past them, every resource connection that moved keeps the path highlight and its arrival cue.
- **Focus mode wins on connections.** Outside the focus set, the path highlight, the token and badge, the depart and arrive cues and the reduced-motion forms are drawn at the connection's own low strength; cues inside a node keep full strength.
- **Reduced motion and high contrast.** Under reduced motion nothing travels: the moved paths, the arrival tells and the `+N` badges stay, static, for the step. In forced colours the highlight is told by width, the badge is a system-colour pill, a covered label gives way, and outside the focus set the cues take a dash instead of fading.

**No migration.** Engine, RNG, files, share links, digests and simulation results are unchanged; Monte Carlo draws no tokens, as before. Five release-note lines in 18 languages, 16 of them without native review. The informational `meta.tool` string is now `loop-studio/0.22.0`.

## v0.21.4 — 2026-10-08

A fix release (issues #338 and #340): while the Canvas is edit-locked, the Controls rail keeps every button where it is, as [`docs/canvas-edit-lock.md`](docs/canvas-edit-lock.md) describes; and on a phone, focusing a text field no longer zooms the page in, as [`docs/mobile.md`](docs/mobile.md) §MV4b describes.
Expand Down
29 changes: 18 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,22 @@ Additional feature-specific design documents (localization, mobile, module
system, large-graph readability, simulation playback, edge routing, data
import, …) live under [`docs/`](docs/).

## Latest — v0.21.4
## Latest — v0.22.0

Playback shows what moved where: the path a Gate took, and every amount beside its moving
marker.

- **The Gate's path lights up**: during a run, each branch a Gate actually sends something
down is highlighted under its moving marker; a branch that carries nothing keeps its usual
look
- **`+N` beside every moving marker**: the amount a connection carries in a step rides
beside its marker as a badge, from `+1`; the connection's own label stays in place and
fades only while the badge passes over it
- **Focus mode wins on connections**: the markers, badges and highlights on connections
outside the focus fade with those connections; in a busy step at most 24 markers move, and
every other connection that moved is highlighted instead

## v0.21.4

A fix release: the canvas controls keep their place while editing is locked, and the phone's
language search no longer zooms the page in.
Expand Down Expand Up @@ -175,16 +190,8 @@ A fix release: values and detail rows sit inside their node.
usual maximum, and a row too long even then ends in "…"; positions, heights, files and
results are unchanged

## v0.21.1

A fix release: Focus mode dims the connections too.

- **Connections outside the focus set fade** like the nodes there, path and arrowhead,
whatever their activator says; before, only the nodes were dimmed
- **What stays at full strength is unchanged**: selection, warnings and the moving markers
of a running simulation; under high contrast a faded connection is a fine dotted line

See [`CHANGELOG.md`](CHANGELOG.md) for the full notes of these releases, v0.21.0 (compact
See [`CHANGELOG.md`](CHANGELOG.md) for the full notes of these releases, v0.21.1 (Focus
mode dims the connections too), v0.21.0 (compact
nodes, so more of a large graph fits in view), v0.20.0 (flow
colours in the minimap and the timeline), v0.19.0 (flow
colours on nodes and connections), v0.18.2 (the
Expand Down
12 changes: 10 additions & 2 deletions docs/large-graph-readability.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,16 @@ connection is 0.5 inside the focus set and 0.26 outside it, Focus winning.
Under forced colours a dimmed connection is not faded (opacity 1); the sparse
`1 5` dash is its tell (§LGR9). Issue #329: until v0.21.1 an inline `opacity`
on every connection path beat this rule, so connections were never dimmed; the
activator's 0.5 is now the `.edge-activator-off` class. The playback cues on a
dimmed connection keep their strength (their Focus policy is #330).
activator's 0.5 is now the `.edge-activator-off` class. Issue #330 PR 1
(v0.22.0): the playback cues on a dimmed connection follow its dimming — the
path highlight, the round token and its `+N` badge, the depart / arrive cues,
the L0 pulse and the reduced-motion forms are drawn at the same 0.26
(`filter: opacity()`, so it multiplies with their own animation); under forced
colours they are not faded either, the highlight takes the `1 5` dash, the
token turns hollow and the badge's border is dashed
(`docs/simulation-playback.md` §PB4.6). The run-in-progress cue and every cue
INSIDE a node (the fired wave, the evaluated mark) keep full strength, as
above.

**A selected node is never de-emphasised.** Selection outranks focus: whatever
the focus set says, a node the user has selected renders at full strength, with
Expand Down
57 changes: 50 additions & 7 deletions docs/simulation-playback.md
Original file line number Diff line number Diff line change
Expand Up @@ -329,13 +329,15 @@ the sum); the **`preparedTransition.events` list keeps every original
resource token (different meaning, own beat);
- a cap `MAX_PLAYBACK_TOKENS` (**12**) per edge per step bounds the per-transfer
breakdown chips (`shown + N == the edge's transfer count`); and a SINGLE global
`MAX_PLAYBACK_TOKENS_TOTAL` (**60**) budget spans **every travelling cue in a
step together** — resource transfer tokens, `trigger` beads and non-zero
`MAX_PLAYBACK_TOKENS_TOTAL` (**24**, issue #330 PR 1; it was 60) budget spans
**every travelling cue in a step together** — resource token-and-badge pairs
(§PB4.6, the dot and its `+N` counted as one), `trigger` beads and non-zero
`label` beads — chosen by the fixed stable key
`(edgeId, cueKind, originalEventIndex)` with `resource < trigger < label`, so
the animated set is deterministic and independent of the order the engine
emitted events in. Past either cap only the animation is skipped — the summed
amount shown is still exact, the engine result and any settle-beat cue
emitted events in. Past either cap only the token is skipped — a resource edge
that moved keeps its **path highlight** and its **arrival cue** (§PB4.6), the
summed amount shown is still exact, the engine result and any settle-beat cue
(`activator`, the committed value / label) are untouched. `activator` never
travels, so it is never budget-gated. An edge emits **at most one** travelling
cue per step (one `data.kind`, one state `mode`, one `StateEvent` per mode —
Expand All @@ -345,6 +347,46 @@ the sum); the **`preparedTransition.events` list keeps every original
per-transition `flowByEdge` identity) — a τ-only frame and every one of the
hundreds of edge consumers share that one result.

**PB4.6 — what moved where: the Gate path, the `+N` badge, the label (issue #330
PR 1, v0.22.0).** A presentation layer over §PB4.1–PB4.5; the engine, the RNG,
files, digests and every PB-INV / PBO-INV invariant are unchanged.

- **The round token stays** wherever it travelled before; one summed token per
connection per step (§PB4.5).
- **`+N` badge.** The step's summed amount is always shown, from `+1`, as a
badge **beside** the token (up and to its right), moving with it. It lives in
the label layer, above every connection label, so the token-and-badge pair is
drawn on top. It replaces the bare number that used to sit above the dot only
when the amount was above 1. The badge is elided with the dot at L0 (§PB4.4).
- **Gate path.** For every Gate, deterministic or probabilistic, every outgoing
connection with a move event this step is **highlighted** (`.pb-path--gate`,
under its token); several can be at once. A branch with no move keeps its
usual look — no faded or crossed-out cue. The highlight lasts from the edge's
onset to settle.
- **Past the 24 pairs** (§PB4.5) every moved resource connection keeps the path
highlight (`.pb-path--over-cap`) and its arrival cue, without a token.
- **The connection's own label never moves.** It dims (`.edge-label--under-token`)
only while its real box overlaps the token or its badge; only the connection's
OWN label is tested, never another connection's. The label's size is read once
per text, never per frame.
- **Focus mode wins on connections** (`docs/large-graph-readability.md`
§LGR2.3): on a connection outside the focus set every playback cue — the path
highlight, the token and badge, the depart / arrive cues, the L0 pulse and the
reduced-motion forms — is drawn at the connection's own low strength (0.26).
Cues inside a node keep full strength.
- **Reduced motion:** no travel. The path highlight (`.flow-edge-pulse`), the
arrival tell (§PBO4) and the `+N` badge stay, static, for the committed step;
the badge sits at the target end, to its left, off the target node, within the
same 24 pairs.
- **Forced colours:** the path highlight is told by width (a 4 px `CanvasText`
line over the 1.5 px connection), never by a low opacity; the badge is a
`Canvas` pill with a `CanvasText` border and amount; a covered label is not
faded but gives way (hidden) for as long as the token or badge covers it;
outside the focus set the highlight takes the connection's `1 5` dash, the
token is hollow and the badge's border dashed.
- **Monte Carlo** draws no tokens, as before. The speed tiers (§PB6.1) are
issue #330 PR 3; until then every speed draws this same form.

## PB5. Pause / Resume

**PB5.1 — Pause keeps the prepared transition.** Pause stops the `τ` clock at its
Expand Down Expand Up @@ -662,9 +704,10 @@ emphasis.
their origins; a net-zero +/− pair still shows both component cues;
`> MAX_PLAYBACK_TOKENS` ⇒ still one token, exact summed label, `+N`
affordance; `> MAX_PLAYBACK_TOKENS_TOTAL` travelling cues across the step
(resource + `trigger` + non-zero `label`, one global budget) ⇒ the
(resource + `trigger` + non-zero `label`, one global budget of 24) ⇒ the
over-budget edges keep their committed value / label and their settle cue
but do not animate; `to` unchanged in every case.
but do not animate; a resource edge among them keeps its path highlight and
arrival cue (§PB4.6); `to` unchanged in every case.
14. **MC / Predict untouched** — a Monte-Carlo run shows no tokens and its result
equals today's oracle; Predict shows numbers, no tokens.
15. **VL / revision carry-over** — GraphDoc bytes, `loop-revision/3` digest, undo
Expand Down Expand Up @@ -757,7 +800,7 @@ oracle of its own**. Its verification is:
checklist (each behaviour → the E2E that locks it) lives in
[`examples/README.md`](../examples/README.md) §5.
- **`e2e/playback-fixture.spec.ts`** — imports that graph and checks the cues,
the 60-token budget, the Bézier/orthogonal `d` fidelity, the L0 / reduced-motion
the 24-pair budget, the Bézier/orthogonal `d` fidelity, the L0 / reduced-motion
elision, and the invariance below.
- **Behavioural specs** — `playback.spec.ts` (Slice 1 state machine),
`playback-choreography.spec.ts` (token + state-event choreography, Pause/speed,
Expand Down
7 changes: 7 additions & 0 deletions docs/visual-language.md
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,13 @@ byte-identical across L2/L1/L0 (§VL12.5).
the ▲/▼ glyph kept), no pulse (instant colour change held for one step),
no card slide-in. All *information* is preserved; only the animation is
dropped.
- **Playback, issue #330 PR 1 (v0.22.0)** (`docs/simulation-playback.md`
§PB4.6): a Gate output that moved is highlighted while its token travels, and
the step's summed amount rides beside the token as a `+N` badge from `+1`.
Under reduced motion the same information is static for the committed step:
the moved path's highlight, the arrival tell, and the `+N` badge at the
target end. A connection's own label never moves; it dims only while the
token or its badge covers it.
- Motion never conveys information that isn't also in a static frame.

---
Expand Down
13 changes: 11 additions & 2 deletions e2e/canvas-refresh-edges.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,10 @@ test.describe('Playback choreography — the token reacts ONLY to real engine ev
// in LoopEdge (not merely CSS-suppressed) — so under `prefers-reduced-motion:
// reduce` there is literally no `animateMotion` / travelling element in the DOM
// to play, freeze, or restart.
// (issue #330 PR 1 — the `+N` badge lives in the label layer; a TRAVELLING one
// is motion, the reduced-motion static one is not)
const MOTION =
'.react-flow__edges animateMotion, .flow-move, .pb-move, .pb-cue, .flow-bead, .flow-trail, .flow-token__n, .state-move, .state-move__n'
'.react-flow__edges animateMotion, .flow-move, .pb-move, .pb-cue, .flow-bead, .flow-trail, .pb-badge[data-playback-badge="travel"], .state-move, .state-move__n'

test.describe('Canvas Refresh PR 2 — reduced motion: the flow bead contract', () => {
test('a real FlowEvent renders NO moving element — a held static highlight + arrival cue instead', async ({ page }) => {
Expand All @@ -153,8 +155,12 @@ test.describe('Canvas Refresh PR 2 — reduced motion: the flow bead contract',
expect(held.anim === 'none' || held.anim === '').toBe(true) // not animating
expect(held.op).toBeGreaterThan(0.1) // and actually visible, not faded away

// issue #330 PR 1 — …and the step's `+N`, static, beside the arrival tell
await expect(page.locator('.pb-badge[data-badge-for="r1"][data-playback-badge="static"]')).toHaveText('+2')

// an edge that carried NOTHING this step gets no highlight (nothing inferred)
await expect(page.locator('.react-flow__edge[data-id="r2"] .flow-edge-pulse')).toHaveCount(0)
await expect(page.locator('.pb-badge[data-badge-for="r2"]')).toHaveCount(0)
})

test('the cues are HELD through Pause and only clear on Reset', async ({ page }) => {
Expand Down Expand Up @@ -197,7 +203,10 @@ test.describe('Playback choreography — one token per event, merged sum', () =>
await step(page)
const token = page.locator('.react-flow__edge[data-id="r1"] .pb-move')
await expect(token).toHaveCount(1)
await expect(token.locator('.flow-token__n')).toHaveText('2') // src → A carries flow "2"
// src → A carries flow "2": the summed amount rides beside the token as its
// `+N` badge (issue #330 PR 1), one per edge
await expect(page.locator('.pb-badge[data-badge-for="r1"]')).toHaveText('+2')
await expect(page.locator('.pb-badge[data-badge-for="r1"]')).toHaveCount(1)

// a multi-step run never shows more than one token on any edge at once
for (let i = 0; i < 5; i++) {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading