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/licence-screen.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "type": "user-facing", "releaseNoteId": "release:0.18.0" }
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ jobs:
run: npm run check:storage-port
- name: Share crypto — key handling and encryption stay in one module, with fixed parameters
run: npm run check:share-crypto
- name: Licence screen — the third-party notices are shown as text, never through an HTML sink
run: npm run check:licence-screen
- name: Change declaration — a change that ships says whether it is user-facing, with its version and release note
run: npm run check:change-declaration
env:
Expand Down
47 changes: 47 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,53 @@ 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.18.0 — 2026-10-05

The third-party open-source licenses, inside the app (issue #301).

- **About Loop Studio has a Third-party open-source licenses button.** It turns the same dialog into a licence view, not a second dialog: the view says first that these are the notices of the third-party software Loop Studio includes and that they cover those components only, not Loop Studio itself, then shows every component's full licence text, in its original English, in a scrollable block that can be searched, selected and copied. Back to About returns to the button; Escape closes the dialog.
- **The text is the build's own notices file**, the one every build has carried since the previous change: the web and installed builds read `THIRD_PARTY_NOTICES.txt` from their own origin (the installed app has it precached, so it opens offline) and also offer it as a text file in a new tab; the single-file version reads the copy inside its own HTML. It is shown as text, never as HTML, and a new check, `check:licence-screen`, fails if that ever changes. Loading and a failed read have their own states, with Try again.
- **The dev server serves the same text**, rebuilt from the committed manifest and the installed packages, and checked against the manifest byte for byte. On the dev server, the production bundle, the installed app offline and the single-file version, the end-to-end tests compare the text on screen with the manifest's SHA-256.
- **The About dialog's links are readable in the dark theme**: they had the browser's default blue.

**No migration.** Nine new interface strings and three release-note lines in 18 languages, 16 of them without native review. The informational `meta.tool` string is now `loop-studio/0.18.0`.

## v0.17.2 — 2026-10-05

A fix release. The Temporary session button in the toolbar was a 20 px pill with 10 px grey text and read as a status badge.

- **It is drawn as the menu buttons beside it**: 28 px tall, the 8 px control radius, 12 px text, the primary ink on the raised face, their hover boundary and their keyboard focus. Its orange border still marks a temporary session at rest. Nothing in how sessions switch or save changed.

**No migration.** The informational `meta.tool` string is now `loop-studio/0.17.2`.

## v0.17.1 — 2026-10-04

A fix release (issue #301). Share links used a bundled compression library whose origin the repository's record does not establish.

- **Share links are compressed and decompressed only with the browser's own Compression Streams**; the bundled code is removed. Every browser the build targets has them. Links the removed code made still open, and the link format, limits and opening order are unchanged.
- **A page without them** makes no plain or protected link, and opening a link there shows one sentence, asks for no password and leaves the open diagram untouched.

**No migration.** The informational `meta.tool` string is now `loop-studio/0.17.1`.

## v0.17.0 — 2026-10-04

Optional password protection for share links (issue #300).

- **Protect with a password**, unticked by default: the diagram is sealed inside the link with AES-256-GCM under a key derived from the password (PBKDF2-HMAC-SHA-256, 600,000 iterations), with Web Crypto only (`docs/specs/SEMANTICS-P.md`). A plain link is created and opened exactly as before.
- **Opening a protected link** checks its structure first, removes the fragment, then asks for the password; nothing from the shared diagram is drawn before a correct one. A wrong password and altered link data give the same message. Loop Studio does not store or transmit the password or the key, and a lost password cannot be recovered.

**No migration.** The informational `meta.tool` string is now `loop-studio/0.17.0`.

## v0.16.0 — 2026-10-04

Storage on a shared computer (issue #297).

- **A storage gate**: before anything stored is read, Loop Studio asks whether this is a personal browser or a shared computer. A temporary session neither reads nor saves stored work, author information or settings; only the start-up choice can be stored. The single-file version asks every time and never remembers.
- **A Storage and privacy area in Settings**: the storage mode, the restore toggle, switching to the other kind of session, deleting the stored work and resetting everything Loop Studio keeps. A failed deletion is never shown as done.
- **A temporary session shows a standing button** with export and the ways out, warns before a reload would lose work, and is asked before the installed app restarts. The share dialog says that the link itself contains the entire document.

**No migration.** The informational `meta.tool` string is now `loop-studio/0.16.0`.

## v0.15.3 — 2026-10-03

A fix release. The symbols on the buttons were characters an operating-system font drew, so a button looked different from Windows to iPhone, and on iPhone some of them were colour emoji.
Expand Down
65 changes: 29 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,53 +140,42 @@ Additional feature-specific design documents (localization, mobile, module
system, large-graph readability, simulation playback, edge routing, data
import, …) live under [`docs/`](docs/).

## Latest — v0.15.3
## Latest — v0.18.0

A fix release: the buttons' symbols are the app's own icons.
The third-party open-source licenses, inside the app.

- **Every functional icon is drawn by the app**, not by an operating-system font, so Play,
the lock, the menu arrows and the rest look the same on Windows, iPhone and everywhere else
- **Names are words**: no symbol is read out before a label, and the Reset and Step buttons
have names
- **About Loop Studio → Third-party open-source licenses** shows the full license text of
every third-party component the build includes, in its original English, in the same
dialog; Back returns to About
- **Offline too**: the installed app has the text precached, and the single-file version
carries it inside the file; the web and installed builds also open it as a text file
- **Loop Studio's own notice is unchanged**: the licenses cover the included components only

## v0.15.2
## v0.17.2

A fix release: the theme you chose comes back when the app starts.
A fix release: the Temporary session button looks like the menu buttons beside it.

- **The saved theme is applied at start-up**, without opening Settings; an unreadable value
follows the system theme
- **No light flash before a dark start**: the theme is read in the page head, before anything
is painted
- **The same height, corners, text size and colours** as the toolbar's menu buttons, and
their hover and keyboard focus; its orange border still marks a temporary session

## v0.15.1
## v0.17.1

A fix release: the playback bar keeps to its space at narrow widths.
A fix release: share links use the browser's own compression.

- **On a phone**, the run bar no longer covers the bottom of the canvas when its buttons wrap
to a second row; the Timeline sheet, the More sheet and the zoom buttons stay above it
whatever its height
- **In a narrow desktop window**, the playback controls are no longer cut off at the bottom
of the window
- **Share links are compressed with the browser's built-in Compression Streams**; the
bundled compression code is removed. Existing links still open and the link format is
unchanged
- **A browser without them** makes no link and says so, and the open diagram is kept

## v0.15.0
## v0.17.0

- **An update notice** — after an update, a browser that has used Loop Studio before sees
one line naming the new version; it takes no focus, changes nothing and never times out
- **What's new, in the Help menu** — every release note, newest first, in all eighteen
languages, offline; a `New` marker stays until the newest entry has been opened
- **A clearer Help menu** — grouped by purpose, with items named for what they do:
`Restart the tour`, `Turn contextual tips back on`
- **A Timeline series selector** — choose which series the chart draws; a document with no
saved choice draws the first eight instead of every line
- **A refreshed light theme** — one calm palette, rounder panels and a clearer keyboard
focus indicator; the dark theme is unchanged
- **Password-protected share links** — an optional password encrypts the diagram inside
the link, in the browser; the password is asked for before anything from the diagram is
shown, and a lost password cannot be recovered. A plain link is still the default

No migration: a v0.14.0 file opens unchanged. From this release on, a visible change ships
with its version and its release note in the same change — see
[`docs/release-notes.md`](docs/release-notes.md).

See [`CHANGELOG.md`](CHANGELOG.md) for the full v0.15.2, v0.15.1 and v0.15.0 notes, the
v0.14.0 and v0.13.0 releases and every earlier one.
See [`CHANGELOG.md`](CHANGELOG.md) for the full notes of these releases, v0.16.0 (the
storage gate, temporary sessions and the Storage and privacy area), the v0.15 releases and
every earlier one.

## Credits

Expand All @@ -199,3 +188,7 @@ publicly documented academic work on game-economy diagrams.
## Copyright

Copyright © 2026 Hanrim. All rights reserved.

Loop Studio includes third-party open-source components. Their licenses are in the app
(About Loop Studio → Third-party open-source licenses) and in every build
([`licenses/README.md`](licenses/README.md)); they cover those components only.
25 changes: 25 additions & 0 deletions docs/guided-tour.md
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,31 @@ Loop Studio는 독립 프로젝트이며 Machinations.io와
- No animation requirement; respects `prefers-reduced-motion` and `forced-colors`
like every other dialog.

**The licence view** (issue #301, v0.18.0)

- Below the non-affiliation sentence, a button, `Third-party open-source
licenses` / `제3자 오픈소스 라이선스` (key `about.licenses`), turns the SAME
dialog into its licence view: no second modal. The title becomes that name;
focus moves to `Back to About`, which returns to About and puts focus back on
the button. `Escape`, the backdrop and × still close the whole dialog, and
the next opening starts on About.
- The view says first, in the UI language, that these are the notices of the
third-party software Loop Studio includes, that they cover those components
only and not Loop Studio itself, and that each licence is shown in its
original English. Loop Studio's own copyright line is unchanged.
- The notices are the build's own `THIRD_PARTY_NOTICES.txt`, untranslated, in a
scrollable, focusable `<pre dir="ltr" lang="en">` that the browser can search,
select and copy. They are a React text child, never HTML
(`npm run check:licence-screen`, and [`licenses/README.md`](../licenses/README.md)).
- Web and PWA read the same-origin file when the view opens (the PWA has it
precached, so it opens offline) and offer `Open as a text file`, a new tab
with `rel="noopener noreferrer"`. The portable file reads the text from its
own `<template id="third-party-notices">` and has no file link. The dev
server answers the file with the web build's text, rebuilt from the
committed manifest.
- Loading and a failed read have their own states; `Try again` reads again,
and `Back to About` works in every state. Nothing is stored.

## GT8. Localization

All copy is in the localization catalog under a `tour.*` namespace, EN canonical
Expand Down
20 changes: 20 additions & 0 deletions e2e/dist.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { expect, test } from './support/loop'
import { capturedExports, installProbe, pathProbe } from './support/mc'
import { expectOneVersionStory, readAboutVersion, readNewestShown } from './support/whatsNew'
import { RELEASE_NOTES } from '../src/releaseNotes/releaseNotes'
import { expectShownNoticesAre, manifestNoticesSha, openAbout, openLicences, sha256 } from './support/licenses'

// Runs under playwright.dist.config.ts: production `npm run build` served by
// `vite preview` at the root `/` — the shape Cloudflare Pages serves. No
Expand Down Expand Up @@ -675,3 +676,22 @@ test.describe('production build: a password-protected share link', () => {
expect(bad).toEqual([])
})
})

// ── issue #301 — the third-party licences, as the production build serves them
test.describe('production build — the third-party open-source licenses', () => {
test('About shows the built THIRD_PARTY_NOTICES.txt as text, and its file link opens the same bytes in a new tab', async ({ page, context }) => {
const { bad } = await openProd(page)
await openAbout(page)
const dlg = await openLicences(page)
await expectShownNoticesAre(page, 'web')
const link = dlg.locator('[data-licenses-file]')
await expect(link).toHaveAttribute('rel', /\bnoopener\b/)
const [tab] = await Promise.all([context.waitForEvent('page'), link.click()])
await tab.waitForLoadState()
expect(new URL(tab.url()).pathname).toBe('/THIRD_PARTY_NOTICES.txt')
expect(await tab.evaluate(() => window.opener)).toBeNull()
expect(sha256(await (await tab.request.get(tab.url())).text())).toBe(manifestNoticesSha('web'))
await tab.close()
expect(bad, 'no failed or cross-origin requests').toEqual([])
})
})
Loading
Loading