From 869b89293edfa1feabc2b52c794dcf65a55421d6 Mon Sep 17 00:00:00 2001 From: Hanrim <148833226+MerciHanrim@users.noreply.github.com> Date: Mon, 5 Oct 2026 14:14:00 +0900 Subject: [PATCH] feat(about): the third-party open-source licenses, in the About dialog (v0.18.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part of #301, the second of its two pull requests. Version 0.18.0. - About Loop Studio gains a "Third-party open-source licenses" button that turns the same dialog into its licence view, not a second modal. 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 the build's own notices, unchanged and in English, in a scrollable, focusable
 that the browser can
  search, select and copy. Back to About returns focus to the button; Escape,
  the backdrop and x close the whole dialog, and the next opening starts on
  About. Loading and a failed read have their own states, with Try again.
- src/licenses/notices.ts reads the text: the same-origin
  THIRD_PARTY_NOTICES.txt on the web and PWA builds (precached by the PWA, so
  it opens offline; a non-plain-text answer such as a host's index.html
  fallback is refused), and template.content.textContent in the portable
  file. The web and PWA builds also offer the file in a new tab with
  rel="noopener noreferrer"; the portable file has no separate file and no
  link. The text is a React text child; a new check, check:licence-screen,
  parses the loader, the view and the dialog with the TypeScript parser and
  fails on any HTML sink (dangerouslySetInnerHTML, innerHTML / outerHTML,
  insertAdjacentHTML, document.write, createContextualFragment, DOMParser,
  setHTMLUnsafe) or a loader that does not read the template's content.
- The dev server serves THIRD_PARTY_NOTICES.txt too (thirdPartyNoticesDev):
  the web build's text rebuilt in memory from the committed manifest and the
  installed packages (noticesFromManifest), with every licence file, NOTICE
  file and the whole text checked against the manifest's SHA-256; nothing is
  written to disk or fetched. A unit test rebuilds all three sections and
  compares them with the manifest.
- e2e: on the dev server, the production bundle, the PWA with the network
  down and the portable file, the text on screen is one text node whose
  SHA-256 is the manifest's for that build; the file link opens the same
  bytes with no opener (offline from the precache on the PWA); a failed read,
  Back, Escape, 18 languages and a 320 px phone are covered. The PWA test
  server now serves .txt as text/plain, as Cloudflare Pages does.
- The About dialog's links take the dialog's ink, underlined: the browser's
  default link blue was unreadable on the dark panel.
- Declared user-facing: version 0.18.0 and release note release:0.18.0, three
  lines; nine new interface strings; all in 18 languages, 16 without native
  review. The per-language copy tests move their pinned counts by those
  twelve keys, as each release does, and declare the loanwords Italian and
  Dutch use (open source, software, copyright) and two Spanish homographs;
  pt-PT stays under its quarter bound. scripts/arrow-units.json records the
  view's external-link icon.
- Docs: docs/guided-tour.md §GT7.1, licenses/README.md, and README's
  release section (it still said "Latest — v0.15.3") and Copyright section;
  CHANGELOG.md gains v0.16.0 to v0.18.0, which it did not have.
---
 .changes/licence-screen.json                  |   1 +
 .github/workflows/ci.yml                      |   2 +
 CHANGELOG.md                                  |  47 +++++
 README.md                                     |  65 +++----
 docs/guided-tour.md                           |  25 +++
 e2e/dist.spec.ts                              |  20 +++
 e2e/licenses.spec.ts                          | 170 ++++++++++++++++++
 e2e/portable-file.spec.ts                     |  13 ++
 e2e/pwa.spec.ts                               |  27 +++
 e2e/support/licenses.ts                       |  49 +++++
 e2e/support/pwa-serve.mjs                     |   2 +
 e2e/whats-new.spec.ts                         |   2 +-
 licenses/README.md                            |   6 +
 package-lock.json                             |   4 +-
 package.json                                  |   3 +-
 scripts/arrow-units.json                      |   3 +-
 scripts/check-licence-screen.mjs              |  33 ++++
 scripts/licence-screen-rules.mjs              |  51 ++++++
 scripts/licence-screen-rules.test.mjs         |  51 ++++++
 scripts/third-party-notices.test.mjs          |  28 +++
 scripts/third-party-notices/core.mjs          |  61 +++++++
 scripts/third-party-notices/vite-plugin.d.mts |   3 +
 scripts/third-party-notices/vite-plugin.mjs   |  40 +++++
 src/components/AboutDialog.tsx                | 102 ++++++++---
 src/components/LicensesView.tsx               |  76 ++++++++
 src/i18n/es419Copy.test.ts                    |   2 +
 src/i18n/itCopy.test.ts                       |   9 +-
 src/i18n/locales/ar/ui.ts                     |  12 ++
 src/i18n/locales/de/ui.ts                     |  12 ++
 src/i18n/locales/en/ui.ts                     |  12 ++
 src/i18n/locales/es-419/ui.ts                 |  12 ++
 src/i18n/locales/es-ES/ui.ts                  |  12 ++
 src/i18n/locales/fr/ui.ts                     |  12 ++
 src/i18n/locales/it/ui.ts                     |  12 ++
 src/i18n/locales/ja/ui.ts                     |  12 ++
 src/i18n/locales/ko/ui.ts                     |  12 ++
 src/i18n/locales/nl/ui.ts                     |  12 ++
 src/i18n/locales/pt-BR/ui.ts                  |  12 ++
 src/i18n/locales/pt-PT/ui.ts                  |  12 ++
 src/i18n/locales/ru/ui.ts                     |  12 ++
 src/i18n/locales/th/ui.ts                     |  12 ++
 src/i18n/locales/tr/ui.ts                     |  12 ++
 src/i18n/locales/vi/ui.ts                     |  12 ++
 src/i18n/locales/zh-Hans/ui.ts                |  12 ++
 src/i18n/locales/zh-Hant/ui.ts                |  12 ++
 src/i18n/nlCopy.test.ts                       |   8 +-
 src/i18n/ptPtCopy.test.ts                     |  13 +-
 src/i18n/ruCopy.test.ts                       |   3 +-
 src/i18n/thCopy.test.ts                       |  10 +-
 src/i18n/trCopy.test.ts                       |   2 +-
 src/i18n/viCopy.test.ts                       |  10 +-
 src/index.css                                 |  63 +++++++
 src/licenses/notices.test.ts                  |  88 +++++++++
 src/licenses/notices.ts                       |  70 ++++++++
 src/releaseNotes/releaseNotes.ts              |   8 +
 vite.config.ts                                |   5 +-
 56 files changed, 1309 insertions(+), 82 deletions(-)
 create mode 100644 .changes/licence-screen.json
 create mode 100644 e2e/licenses.spec.ts
 create mode 100644 e2e/support/licenses.ts
 create mode 100644 scripts/check-licence-screen.mjs
 create mode 100644 scripts/licence-screen-rules.mjs
 create mode 100644 scripts/licence-screen-rules.test.mjs
 create mode 100644 src/components/LicensesView.tsx
 create mode 100644 src/licenses/notices.test.ts
 create mode 100644 src/licenses/notices.ts

diff --git a/.changes/licence-screen.json b/.changes/licence-screen.json
new file mode 100644
index 00000000..6507017b
--- /dev/null
+++ b/.changes/licence-screen.json
@@ -0,0 +1 @@
+{ "type": "user-facing", "releaseNoteId": "release:0.18.0" }
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 3aa4b5bc..37b7ec00 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -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:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b04c1cde..5179fb4f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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.
diff --git a/README.md b/README.md
index 2d4753c2..0dc8eac6 100644
--- a/README.md
+++ b/README.md
@@ -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
 
@@ -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.
diff --git a/docs/guided-tour.md b/docs/guided-tour.md
index eaf51684..9a49e24f 100644
--- a/docs/guided-tour.md
+++ b/docs/guided-tour.md
@@ -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 `
` 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 `