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/diagram-grid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "type": "user-facing", "releaseNoteId": "release:0.25.0" }
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,15 +114,15 @@ jobs:
# within the budget (`npm run check:e2e-shards`). To change the shard count,
# change the matrix AND the `/N` in the run step; the check keeps them equal.
e2e-shard:
name: e2e (shard ${{ matrix.shard }}/5)
name: e2e (shard ${{ matrix.shard }}/6)
# Windows so the committed visual baselines (`*-win32.png`) match; public
# repo ⇒ Actions minutes are free.
runs-on: windows-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4, 5]
shard: [1, 2, 3, 4, 5, 6]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
Expand All @@ -135,7 +135,7 @@ jobs:
# the default config's `build-portable` setup project self-builds the
# portable bundle on the one shard that carries the portable spec
- name: E2E (dev server + portable file://)
run: node scripts/e2e-shards.mjs run ${{ matrix.shard }}/5
run: node scripts/e2e-shards.mjs run ${{ matrix.shard }}/6
# the JSON report is the timing sample `scripts/e2e-shard-weights.mjs`
# reads to refresh the weights; small, kept for two weeks, uploaded always
- name: Upload the timing report
Expand Down
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,26 @@ 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.25.0 — 2026-10-10

Diagrams are easier to read, in one migration (issue #344, Diagram Readability): nodes line up on a grid, connections run at right angles around them, and every node keeps its text inside its outline, as [`docs/diagram-layout.md`](docs/diagram-layout.md), [`docs/edge-routing.md`](docs/edge-routing.md) §ER14–§ER16 and [`docs/specs/SEMANTICS-R10.md`](docs/specs/SEMANTICS-R10.md) describe.

- **The port row.** A node's resource ports (`in` / `out`) sit on a fixed row 28 px below its top, on its drawn outline, whatever its height, text or language. A node with a long name grows downward and its connections do not move; one-line nodes draw their ports where they always did.
- **The 16 px grid.** A node is on the grid when its left edge and its port row are multiples of 16, so two nodes on one grid row have level connections. A palette click places the new node on the nearest free grid spot from the canvas centre (no random offset any more); a drop, Insert module and an imported Parameter land on the grid.
- **Dragging snaps, with smart guides.** A dragged selection moves as one, so it keeps its shape; a frame drag snaps the frame's top-left and a resize the dragged corner. Within 6 screen px a drag is pulled to another node's port row, then to its centre or edges, then to the grid; a dashed line shows where it will land and a solid line reaches every node it lines up with. Where a node is grabbed never changes where it lands. After an arrow-key move its row and column show for a moment. The arrow keys move a node or a frame one grid step, 16 px (Shift: 64 px), instead of 5 px (20 px). The phone shows no guides.
- **Modifiers.** Alt now means a free move, off the grid, for nodes, selections, frames, drops and bend points; the guides show faint and pull nothing. Moving a frame without its contents is Ctrl (⌘ on a Mac), no longer Alt; the frame hint says so. Shift is unchanged.
- **Automatic orthogonal connections.** New connections are orthogonal: drawn at right angles and routed around every node, their own two nodes included, leaving and entering each node along a short stub. Where room is short the clearance shrinks, then the route goes around the outside of the diagram; only a port that another node covers is crossed, by that one node. Connections from the same port share only its 16 px stub and branch apart there, so none run on top of each other past it. A label takes the first free spot on its own line, clear of every node and label, and a connection that runs through other labels is routed again when that crosses fewer. A run, a selection or a zoom never moves a route.
- **Connection shapes and bend points.** A selected connection's Route offers Orthogonal, Curved (the earlier curve) and Straight. An orthogonal connection is automatic until it has a bend point: Add bend, then a click on the line, or Enter, adds one without moving the line; a bend point is dragged, moved with the arrow keys (16 px, Shift: 64 px) or removed with Delete, snaps to the grid (Alt: freely) and makes the route manual. Reset to automatic removes the bend points. Each add, move, delete, reset or shape change is one undo step; nothing changes while editing is locked. Curved and Straight connections do not avoid nodes, but their labels still take a free spot.
- **The phone shows, and does not edit.** It draws every shape, route and bend point; route editing is desktop only.
- **Text inside the outline.** The Pool, Source, Drain, Converter and Gate are drawn for their own width: their slanted sides, notch, point and waist keep a fixed depth instead of stretching, and the title, value and every other line stay at least 8 px inside the drawn outline, in every language. Ports and the Converter's conversion mark sit on the outline as drawn.
- **Older diagrams are lined up once.** An ordinary document saved by an earlier version — a graph file, Workspace, share link or autosave — is re-placed once when it opens, before its undo history starts, so the conversion is never an undo step. Nodes move to the grid; related rows a few pixels apart merge into one; nodes closer than 48 px across or 32 px down are moved apart by inserting space, so no left / right or above / below order is reversed; each frame is rebuilt around the nodes it held and the nodes that sat in it, keeping its padding (at least 24 px); waypoints snap (one that would land inside a node is dropped); and every connection without a shape becomes automatic orthogonal. The re-placement uses each node's widest size over the 18 languages, so it is the same in every language. Records are not converted (see Migration).
- **Tidy to grid.** A new button in the canvas controls (desktop) applies the same re-placement to the open diagram at any time, graph and frames together, as ONE undo step; with nothing to move it adds no step, and it is off while editing is locked. On a revision-based document it changes only the current document, like any other edit. It never changes a connection's shape.
- **The Templates.** All five bundled Templates are placed again on the grid by their widest boxes in all 18 languages, every connection automatic orthogonal with no hand-placed bend point: no connection runs through a node, no label sits on a node, no frame cuts a node and two frames keep at least 32 px apart. The coffee roastery Template opens on its operating flow at about 0.5 in a 1280 x 800 window. Early MMO opens on its first steps at about 1.04 in a 1600 x 1000 window, with Character creation, Active character, Starter encounters and Starter Lv 1–5 whole in every language, with the minimap open or collapsed. The gacha Template keeps its overview. Reset view and the minimap show the whole diagram.

**Migration.** Files now carry `layoutVersion` (1), written on every save; a file without it reads as 0 and is re-placed once on opening. It is not part of any digest. Records keep their recorded layout and are never converted automatically: a project revision and a proposal payload — opened, opened as a document or applied — and a Project autosave based on a revision keep their positions and connection shapes, and the base revision and the proposal source stay as recorded. Their digests cover the positions; converting them would make a document differ from its own revision the moment it opens and turn every exact proposal into a non-exact one. This is intended: Review shows the real layout differences between a converted document and its recorded revision. Such a record also keeps its curved connections' labels where they were recorded until its first shape change or Tidy to grid. A position placed freely with Alt in a current document is kept. A Straight connection is stored as `route: "straight"` (`loop-revision/10`); a diagram without one has the same revision bytes and digests as before. The engine digest and simulation results are unchanged; the content digest of a re-placed diagram or Template changes with its positions and shapes. 17 strings in 18 languages (15 added, 2 revised: the connection shape and bend point controls, the connection note, Tidy to grid, the frame hint and five release-note lines), 16 of them without native review. The informational `meta.tool` string is now `loop-studio/0.25.0`.

**Compatibility.** v0.24.0 and earlier open a v0.25.0 diagram with a Straight connection but draw that connection Curved, and refuse the project header of such a project revision, without a warning; saving it again there drops `route: "straight"`, so the Straight shape is lost. Opening a v0.25.0 document in an earlier version and saving it is not supported.

## v0.24.0 — 2026-10-09

Playback speed changes what a step draws (issue #330, the last of three), and the phone gets a speed choice of its own, as [`docs/simulation-playback.md`](docs/simulation-playback.md) §PB6.1 and [`docs/mobile.md`](docs/mobile.md) §MV4 describe.
Expand Down
36 changes: 20 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,24 @@ Additional feature-specific design documents (localization, mobile, module
system, large-graph readability, simulation playback, edge routing, data
import, …) live under [`docs/`](docs/).

## Latest — v0.24.0
## Latest — v0.25.0

Diagrams are easier to read: nodes line up on a grid, connections run around them at right
angles, and every node keeps its text inside its outline.

- **Grid-aligned editing**: nodes and frames snap to a 16 px grid, with smart guides to other
nodes' ports, centres and edges; an older ordinary document is lined up once when it opens,
and Tidy to grid realigns the open one at any time as one undo step
- **Clearer automatic routing**: orthogonal connections go around nodes, branch apart at a
shared port and put their labels on a free spot of their own line
- **Editable connection shapes**: Curved, Straight or Orthogonal, automatic or with bend
points added, moved or removed on the desktop; the phone shows them without editing
- **Text inside nodes**: Pools, Sources, Drains, Converters and Gates are drawn to their own
width, so titles and values stay inside in all 18 languages
- **Reworked Templates**: all five use the new grid and routing, with clearer spacing and
opening views

## v0.24.0

Playback speed changes what a step draws, and the phone gets a speed choice of its own.

Expand Down Expand Up @@ -179,21 +196,8 @@ marker.
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.

- **The frame buttons stay, turned off**: Group frame and Clear all frames remain in the
canvas controls while editing is locked, so the other buttons no longer move; before, the
two disappeared and every button above them shifted down
- **Locking turns the frame tool off**: a drag on the empty canvas then moves the view, as it
should while editing is locked
- **No zoom from the phone's text fields**: on an iPhone, tapping the language search used to
zoom the page in and leave it zoomed; every phone text field is now large enough that it
does not

See [`CHANGELOG.md`](CHANGELOG.md) for the full notes of these releases, v0.21.3 (the edit
See [`CHANGELOG.md`](CHANGELOG.md) for the full notes of these releases, v0.21.4 (the
canvas controls keep their place while editing is locked), v0.21.3 (the edit
lock is exact, and its button shows the state), v0.21.2 (values
and detail rows sit inside their node), v0.21.1 (Focus
mode dims the connections too), v0.21.0 (compact
Expand Down
6 changes: 4 additions & 2 deletions docs/ci-e2e-shards.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# The e2e shards (issue #311)

The browser suite (`npm run e2e`: the dev-server projects and the portable file) runs on CI as five concurrent jobs on `windows-latest`, each with a 20-minute limit. This page says how the suite is divided between them, what proves the division is complete, and how the division is kept honest as the suite grows.
The browser suite (`npm run e2e`: the dev-server projects and the portable file) runs on CI as six concurrent jobs on `windows-latest`, each with a 20-minute limit. This page says how the suite is divided between them, what proves the division is complete, and how the division is kept honest as the suite grows.

## Why not Playwright's `--shard`

Expand Down Expand Up @@ -29,6 +29,8 @@ It runs in the `checks` job on every pull request and push.

Predicted at the first split (weights from six runs, 2026-10-01 to 2026-10-03), before any CI run of it: with four shards the loads would be 808 / 808 / 809 / 808 s by the medians and 894 / 919 / 882 / 919 s by the slowest samples, a longest job of 17 min 19 s and only 41 s inside the budget; so the split ships with **five** shards: 648 / 647 / 647 / 646 / 646 s by the medians, 712 / 746 / 708 / 725 / 723 s by the slowest samples, a longest predicted job of 14 min 26 s, 5 min 34 s under the limit. Against each of the eleven recorded runs, with that run's own times and weights that exclude it, the longest of four time-cut shards would have been 9 to 18 % shorter than the count split that ran (848 s instead of 1,037 s on `16020f2`), and the longest of five 606 to 693 s. These are predictions from recorded samples; the measured shard times of the first CI runs are what confirms them.

Grown to **six** shards on 2026-10-10 (issue #344, v0.25.0). The weights had last been refreshed on 2026-10-03, and the suite had since grown to 2,105 tests with spec files that had no sample (estimated at the suite's median per test). On run 38032960035 (`30bf3d4`) four of the five shards ran their tests in 15.0 to 16.6 min, and shard 4 was cancelled at the 20-minute limit after 379 of its 386 tests, against a prediction of 14 min 53 s. That run was added as a sample: the four uploaded reports, and for shard 4, which uploads no report when cancelled, the durations its log printed for the 379 tests that ran plus the local full run's durations for the 7 it never reached (about 6.5 s together). With those weights five shards predict longest jobs of 19 min 10 s, 50 s under the limit and so outside the budget; six predict 14 min 46 s to 16 min 01 s by the slowest samples, 3 min 59 s to 5 min 14 s under the limit.

## Keeping the weights current

Every shard job uploads its JSON report (`test-results/e2e-report.json`) as the artifact `e2e-report-shard-<i>`, kept for two weeks. To add a run as a sample:
Expand All @@ -44,4 +46,4 @@ The newest sample goes to the front of every file it covers and the seventh samp

- It does not make a shard finish in time on a runner slower than any in the sample, and it does not say why two runners differ; that variance (up to ±50 % per shard on the same tree) is recorded, not explained.
- It does not touch the production-bundle and PWA jobs, which are not sharded, and it does not change the 20-minute limit.
- It does not split a spec file. The largest file (`large-graph-readability.spec.ts`, about 210 s) is well under a shard's share, so file granularity does not bind; if a single file ever approached a shard's share, the unit would have to change.
- It does not split a spec file. The largest files (`whats-new.spec.ts`, `large-graph-readability.spec.ts` and `forced-colors-edge-tell.spec.ts`, about 200 to 225 s each in the 2026-10-10 sample) are well under a shard's share, so file granularity does not bind; if a single file ever approached a shard's share, the unit would have to change.
2 changes: 1 addition & 1 deletion docs/contextual-inline-help.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ to fold them in, that is its own small PR, not part of shipping new hints.
| 6 | Run / simulation error explanation | Register / expression errors are already inline-localized at the point of failure (`error.M_REG_*.message`, `error.EXPR_*.message` in the Inspector) | — | — | **Backlog** — re-assess only if real user confusion shows up; today's inline error codes may already be enough |
| 7 | Module-insert discovery | no natural one-shot "first encounter" moment the way MC/Review have an open event — the Insert-module menu is just always present in the toolbar | — | — | **Backlog** — revisit with real usage signal |
| 8 | Frames (as their own hint) | — | — | — | **Folded into #4** — the Focus/Filter hint copy names frames as a related tool instead of adding a fourth canvas hint competing for the same `top-center` slot |
| 9 | **Frame move** (added 2026-09-20 with the frame-drag carry, §LGR6.5) | the first time a **saved** frame is **selected** on an editable desktop canvas (not mobile, not edit-locked), tour idle | canvas `top-center` `<Panel>` (`hint-note`) — "Drag a frame's edge to move it together with everything inside it. Hold Alt while dragging to move the frame alone." | persisted (`frame-move`) | **Yes** — tier 1 (follows a deliberate action, like #import); it takes the slot over the tier-3 discovery hints and the auto-frame note but **yields to the import note** (`ready = tourIdle && !importHintShowing`, so it is neither rendered nor consumed while that one shows and appears once it closes if the frame is still selected); listed in `Contextual help` for re-arm. Distinct from #8: not a discovery hint, and it only appears once the user has already made a frame. |
| 9 | **Frame move** (added 2026-09-20 with the frame-drag carry, §LGR6.5) | the first time a **saved** frame is **selected** on an editable desktop canvas (not mobile, not edit-locked), tour idle | canvas `top-center` `<Panel>` (`hint-note`) — "Drag a frame’s edge to move it together with everything inside it. Hold Ctrl (⌘ on a Mac) while dragging to move the frame alone, or Alt to move it freely, off the grid." (Ctrl / ⌘ since #344; Alt before) | persisted (`frame-move`) | **Yes** — tier 1 (follows a deliberate action, like #import); it takes the slot over the tier-3 discovery hints and the auto-frame note but **yields to the import note** (`ready = tourIdle && !importHintShowing`, so it is neither rendered nor consumed while that one shows and appears once it closes if the frame is still selected); listed in `Contextual help` for re-arm. Distinct from #8: not a discovery hint, and it only appears once the user has already made a frame. |

**#1 empty canvas** — copy names the palette and "or start from a Template,"
pointing at the same two entry points the tour's steps 1 and 6 named, now
Expand Down
Loading
Loading