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/label-avoids-own-bends.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "type": "user-facing", "releaseNoteId": "release:0.28.1" }
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ 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.28.1 — 2026-10-14

A connection's label keeps off the bend points you placed on it, where its line has room ([`docs/edge-routing.md`](docs/edge-routing.md) §ER14.3); the Early MMO example leaves the Templates menu.

- **The rule.** A routed label takes the first free slot of its own line; free now also means clear of a 16 px square on each of the connection's own STORED bend points (the 12 px bend marker plus 2 px), in the first two placement passes. The last-resort passes ignore the bends, so a line with no slot clear of them still has its label, possibly over a bend: the overlap is avoided where a slot allows, not ruled out. The rule reads stored data only, so selecting or deselecting a connection, or zooming, does not by itself move a label.
- **Measured on the screen** (the bundled examples with bends added to one connection at a time, its rendered label compared with its rendered bend markers): a label touching one of its own markers went from 23 / 202, 49 / 262 and 29 / 254 (three ways to place the bends) to 1, 6 and 0. Most of the 7 left are short lines with bends close together.
- **Other connections.** Labels are placed in order and lines are rerouted around labels, so a label that moves off a bend can move another one: in the same measurement another label moved in 18 of 934 cases and another line changed in 20, in Early MMO, the gacha banner and the playback choreography; none in the other examples. In one MMO case a neighbouring line now runs along a different line for longer and its label sits on that line; it did not reproduce in the two small diagrams checked in the browser, so it is recorded as a limit of that stress case.
- **Early MMO leaves the Templates menu.** The Early MMO progression example is no longer offered by Templates ▾ or the phone's More → Templates; it stays in the repository — `examples/mmo-progression.json`, its Template entry and its translations — as developer stress material for large diagrams. A document made from it still opens, from its file, an autosave or a share link, and its node names still follow a language switch. The menu checks (first screen, language) moved to Coffee and the gacha banner; MMO's own behaviour and performance are still tested, by opening its file. Its two colour-view baselines were removed with its menu case.

**Notes.** Four What's new lines in 18 languages: the translation contract tests pass, and none had native-speaker review. The router's port grouping by face and React Flow's one-render gap after a change of face are separate, later work.

## v0.28.0 — 2026-10-14

A connection can be turned around without drawing it again — the second line-editing release ([`docs/edge-routing.md`](docs/edge-routing.md) §ER19).
Expand Down
47 changes: 20 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ session neither reads nor saves stored work, author information or settings
([docs/storage-sessions.md](docs/storage-sessions.md)).

**Run it now: <https://cozy-loop-studio.pages.dev>** — available in
[18 languages](#languages), with five bundled Templates ranging from a small
production flow to a large game economy and a three-zone probability/pity
comparison.
[18 languages](#languages), with four bundled Templates ranging from a small
production flow to a coffee roastery's daily operations and a three-zone
probability/pity comparison.

![Loop Studio's two-tier toolbar in a temporary session, its orange-bordered Temporary session button beside the menu buttons, and the Coffee roastery Template in the compact new nodes, each name outside its node, grouped into three labelled zone frames with its money in $/day, a few steps into a run with the Timeline filling in below](docs/assets/hero-coffee.png)

Expand Down Expand Up @@ -140,7 +140,20 @@ Additional feature-specific design documents (localization, mobile, module
system, large-graph readability, simulation playback, edge routing, data
import, …) live under [`docs/`](docs/).

## Latest — v0.28.0
## Latest — v0.28.1

A connection's label keeps off the bend points you placed on it, where its line has room.

- **Labels clear of your bends**: where there is room, a connection's label is placed clear of
the bend points you added to it; where there is not enough room, it can still overlap one
- **Stays put**: selecting a connection, deselecting it or zooming does not by itself change
where its label sits
- **Other connections**: in a dense diagram, a label that moves off a bend can also move the
label or the line of another connection
- **Early MMO example**: no longer listed under Templates; it stays as developer test material
for large diagrams, and a document you already made from it still opens and works as before

## v0.28.0

A connection can be turned around without drawing it again.

Expand Down Expand Up @@ -193,29 +206,9 @@ between.
The badges of two connections that leave a node together can still meet briefly when they
start at different moments, and amounts at the middle zoom level are not yet easier to read.

## v0.26.0

A new node: a compact body with its role and value inside and its name just outside, so the
size of a node no longer depends on the length of a translation.

- **New node design**: the name sits outside the body and stays visible at the middle zoom
level; a value too long for the body ends in an ellipsis and stays complete in its tooltip
and in the Inspector
- **Connections from any side**: a connection can leave or enter a node at its top, bottom,
left or right, and a new connection remembers the side it was drawn from; existing documents
are not re-placed, their values and the meaning of their connections are kept, and they look
different with the new node
- **Notices under the toolbar**: the update notice and the first-time hints sit in a strip
under the toolbar, and the diagram does not move when they appear or close
- **A Pool's current amount**: a read-only field in the Inspector, beside the starting
amount, also on phones
- **Re-placed Templates**: the Coffee roastery (money in $/day) and the 3-zone gacha banner
comparison are laid out again for the new node

A document with chosen sides, saved again by v0.25.0 or earlier, loses those sides. v0.26.1
corrects the wording of two lines of this release's What's new note.

See [`CHANGELOG.md`](CHANGELOG.md) for the full notes of these releases, v0.25.0 (nodes
See [`CHANGELOG.md`](CHANGELOG.md) for the full notes of these releases, v0.26.0 (the
compact node with its name outside, connections from any side, notices under the toolbar),
v0.25.0 (nodes
on a grid, orthogonal connections around them, editable connection shapes and text inside
nodes), v0.24.0 (three
playback speed tiers, and a speed choice and fewer moving markers on the phone), v0.23.0 (a Pool
Expand Down
2 changes: 1 addition & 1 deletion docs/diagram-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ The Controls rail's **Tidy to grid** re-places the current document by the DL2.2
- DL5.3 A saved frame travels with the nodes it held (centre inside it before the move): it is rebuilt around them, keeping its own padding on each side and at least 24 px (§CR17), snapped outward. No frame cuts a node, by the widest boxes. Two frames that stood apart keep at least 32 px between them (`FRAME_GAP`; a frame's title sits in that gap, and two margins alone would make neighbouring frames touch): space is inserted as in §DL2 — every node beyond the nearer frame's far edge moves by the same grid step — so no order is reversed and no clearance shrinks.
- DL5.4 Every connection is Auto orthogonal. A Manual exception (bend points) is used only where placement cannot resolve a case, each recorded with its connection id and reason; none is needed: in all 18 languages no route is blocked or on the outer corridor and no fan overlaps past its stub (measured 2026-10-10).
- DL5.5 The menu-open framings (`initialView`):
- MMO puts readability before the whole start path, and the core start nodes before the zoom: its rect is sized for about 1.05 at 1600 x 1000, inside the Controls rail's reserved column, and its `keep` rect (§DL5.6) caps the zoom so every core start node opens whole in every language — Character creation, Active character, Starter encounters, Starter Lv 1–5. At 1600 x 1000 it opens at 1.040 (node text 12.5 px smallest, 14.6 px median; Thai Starter Lv 1–5, the widest, ends 8 px inside the pane, above the minimap); at 1280 x 800 at 0.725, or 0.772 with the minimap collapsed. Later paths may start off screen; Reset view and the minimap show the whole graph.
- MMO puts readability before the whole start path, and the core start nodes before the zoom: its rect is sized for about 1.05 at 1600 x 1000, inside the Controls rail's reserved column, and its `keep` rect (§DL5.6) caps the zoom so every core start node opens whole in every language — Character creation, Active character, Starter encounters, Starter Lv 1–5. At 1600 x 1000 it opens at 1.040 (node text 12.5 px smallest, 14.6 px median; Thai Starter Lv 1–5, the widest, ends 8 px inside the pane, above the minimap); at 1280 x 800 at 0.725, or 0.772 with the minimap collapsed. Later paths may start off screen; Reset view and the minimap show the whole graph. Since 0.28.1 MMO is not in the Templates menu (a developer stress fixture opened by Import), so this view, applied on a menu open only, no longer applies in the product; it stays in the entry.
- Coffee opens on its operating flow — the five daily levers, the supply and the roasting & sales frames — at about 0.5 in a 1280 x 800 window (L1 with room, never at the 0.45 edge; floor 0.46); the forecast metrics may start off screen.
- Gacha keeps its overview: the comparison row and the Free zone, at its 0.45 floor.
- DL5.6 `keep` — the nodes that must open whole (`InitialView.keep`, `viewportForRect` in `src/components/canvasFit.ts`). An `initialView` MAY carry a `keep` rect: the core start nodes at their widest box over the 18 languages (`templateBoxes.json`), the tight union of those boxes (pinned by `src/model/templates.initialView.test.ts`). The zoom is the rect's fit, capped at the largest zoom that shows `keep` inside the pane (8 px from its right and bottom edges, clear of the Controls rail); the minimap's corner (224 x 176 px with its margin) counts only when `keep` would overlap it on BOTH axes, and then `keep` clears it by whichever side keeps the larger zoom. So a top row may run past the minimap's column above it, and a collapsed minimap never lets the zoom grow past what `keep` allows. The readability floor (`minZoom`) still wins on a pane too small for `keep` (a phone opens at the floor, as before). Only MMO carries `keep`; the left alignment, the vertical centring and the other Templates' views are unchanged.
8 changes: 8 additions & 0 deletions docs/edge-routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -781,6 +781,14 @@ pinned single-edge core (golden + differential tests); the app no longer calls i
- A label takes the first free slot on its own route — the midpoint, then 0.4,
0.6, 0.3, 0.7, 0.25, 0.75, 0.2, 0.8 of its length — clear of every node box
(2 px margin) and every label already placed; the midpoint when none is free.
- A label also keeps off its own connection's STORED bend points
(`waypoints`): a `BEND_LABEL_BOX` (16 px) square on each, the 12 px bend
marker plus 2 px, in the first two passes (clear of everything, then clear of
nodes and labels). The bend markers are drawn there while the connection is
selected; the rule holds whether it is selected or not, so a selection never
moves the label. The last-resort passes ignore the bends, so a line with no
slot clear of them still has its label, possibly over a bend: the overlap is
avoided where a slot allows, not ruled out (share notes 50 and 52).
- Then up to `LABEL_ROUNDS` (2) guarded rounds: an edge whose route runs
through other edges' labels is routed again with those labels as soft
obstacles (`computeSoftReroute`: one rung of `SOFT_PADS` — 12, then 6 px —
Expand Down
11 changes: 11 additions & 0 deletions docs/example-mmo-progression.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ invariants** in the acceptance conditions (§EM10.1). This is a **non-frozen** d
marker — and merges as *settled design, implementation pending*, like
[`docs/localization.md`](localization.md) and [`docs/guided-tour.md`](guided-tour.md).

> **0.28.1 (2026-10-14) — no longer in the Templates menu.** The example is now
> a **developer stress fixture**: it stays a Template entry in
> `src/model/templates.ts` with `menu: false` (so its translations and the
> §TLO11 locale switch still apply), keeps `examples/mmo-progression.json`, its
> fixture, its scripts and its tests, and is opened by **Import**. Templates ▾
> and the phone's More → Templates no longer list it; a document made from it
> still opens. A change that only affects it does not block a release; a
> readability problem it shows is fixed only when a small case reproduces it.
> The design below, including the menu registration (§EM10), is the record of
> how it shipped.

The example is the **second follow-up** to the localization base (after the
guided tour, before contextual inline help). It is a **product demo / Templates
entry**, not a precision instrument and not a test oracle: it exists to show
Expand Down
7 changes: 7 additions & 0 deletions docs/mmo-multilingual-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ toolbar responsive PR). The bundled **MMO progression** template (97 nodes /
144 edges) has node-box overlaps and an unreadable initial camera; the problem
is worst in JA but is **not JA-only**.

> **0.28.1 (2026-10-14).** MMO left the Templates menu and is a developer stress
> fixture opened by Import ([`docs/example-mmo-progression.md`](example-mmo-progression.md)).
> Its `initialView` (§MML3) is applied on a fresh menu open only, so it no
> longer applies in the product; it stays in the Template entry, and the
> menu-framing contract is checked on Coffee and the gacha banner instead. The
> layout measurements below are the record of the MMO pass.

## MML0. What the measurement pass found

All figures are **flow coordinates** (measured at zoom = 1, so screen px = flow
Expand Down
5 changes: 5 additions & 0 deletions docs/product-direction.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,11 @@ own default open-state and its own messaging in the Templates menu.**
That is the single messaging bug this table exists to prevent.
- *Early MMO progression* stays an **Example**, stays edit-locked, is **not**
"fixed" or demoted. It did its job: it verified the ceiling.
**Superseded 0.28.1 (2026-10-14):** it left the Templates menu and is now a
developer stress fixture — still a Template entry (`menu: false`) and still
edit-locked when opened, opened by Import, its documents still open. A change
that only affects it does not block a release
([`docs/example-mmo-progression.md`](example-mmo-progression.md)).

---

Expand Down
3 changes: 2 additions & 1 deletion e2e/dense-graph-pan.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { readFileSync } from 'node:fs'
import type { Page } from '@playwright/test'
import { expect, importGraph, openApp, resetAll, test } from './support/loop'

/** the bundled Early-MMO Template — 97 nodes / 144 edges, real Bézier +
/** the Early-MMO document (a developer stress fixture since 0.28.1, read from
* its file) — 97 nodes / 144 edges, real Bézier +
* orthogonal routing, real crossings. Used only for the dense-graph
* edge-tap performance regression test below. */
const readMmoProgression = (): string =>
Expand Down
7 changes: 5 additions & 2 deletions e2e/descriptive-copy-wrapping.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,9 @@ test.describe('Korean keeps its wrapping contract', () => {
[...new Set(rows.map((r) => r.wordBreak))],
'Korean descriptive copy keeps `keep-all`',
).toEqual(['keep-all'])
expect(rows.reduce((n, r) => n + r.lines, 0), 'the agreed Korean line count').toBe(38)
// 0.28.1: 36, was 38 — the Early MMO blurb (2 lines, measured in the
// Templates menu's own box) left the menu with its entry
expect(rows.reduce((n, r) => n + r.lines, 0), 'the agreed Korean line count').toBe(36)

// measured, not assumed: a break is mid-eojeol when two adjacent
// NON-space characters end up on different lines
Expand Down Expand Up @@ -170,7 +172,8 @@ test.describe('English is untouched', () => {
await openApp(p)
const rows = await sweepDescriptiveCopy(p)
expect(overflowing(rows).map(describe1)).toEqual([])
expect(rows.reduce((n, r) => n + r.lines, 0), 'the English line count is unchanged').toBe(40)
// 0.28.1: 38, was 40 — the Early MMO blurb (2 lines) left the Templates menu
expect(rows.reduce((n, r) => n + r.lines, 0), 'the English line count is unchanged').toBe(38)
await ctx.close()
})
})
Expand Down
Loading
Loading