Repository navigation
The update button opens a window with the patch notes, Cancel and Install; built once in core (#28) - #30
Merged
Conversation
…nd Install; built once in core (#28) The chip used to download and quit on a click. It now opens a window that lists what changed, as PLAIN TEXT parsed from the release body (never HTML, never markdown, never a link), with Cancel and Install. Built in core/ so both apps show the same thing (owner: "yes keep the core"), which widens the core from "the terminal" to "what the two apps share"; core 0.2.0 -> 0.3.0. - core: updateTypes, releaseNotes (parser), updatePreview (--preview-update fake: no network, no file, no process, no quit), updateFlow + useUpdateFlow, UpdateChip + UpdateDialog (props only). - The chip never changes width: every label is laid out in one cell. It did change before (104.8 / 54.8 / 96.0 px, measured). - The label ink follows what is behind it (a clipped second copy), after the e2e screenshot showed white "35%" on pale blue on a light theme. - Install asks first while an agent is working; nothing in this app did. A failed download now says so under the chip. - e2e: updateWindow, updateGuard, updateNotes, updateQuiet. App 0.4.0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ing app (#28) The app is single-instance and usually already running when somebody tries the flag, so that launch hands its command line over and ends. updateQuiet now proves the running app honours it, opens no tab for it, and still never asks GitHub. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… Tab trap leaves Ctrl+Tab alone (#28) Review of #28. The app's chords still work over the update window, so Ctrl+W on a tab that hosts an agent (or Alt+F4) raised the close question UNDER it: same z-index, earlier in the document, with the focus on "Close tab" where nobody could see it. Enter, aimed at Install, ended the agent. App now cancels the update window whenever a question appears, and updateGuard holds it (mutation-checked: with the effect off the new check fails). The dialog's Tab trap also handled Ctrl+Tab, which the app's own capture listener had already used to step the tab strip: one press did both. core/README.md gains the host rule for Prism's half. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…pped prose is one entry (#28) Review of #28. The author tail took ANY word after "in", so a title such as "Mention people by @handle in comments" was cut down to "Mention people". What follows "in" must now be a url, which is what generate-notes writes; a bare "by @user" at the end still goes. And a hand-written paragraph wrapped at a column came out as one bullet per line; consecutive plain lines are one entry now, ended by a blank line, a heading or a bullet. Both apps' newest real release bodies were parsed by hand as a check. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…nger re-registers on every render (#28) Found as a flake in the new updateGuard check (about one run in four): Escape did nothing, with the question on screen and focused. Dialog keyed its keydown effect on an inline onCancel, so the listener was removed and re-added on every render of the app. When another window keydown listener sets state during the same Escape, React flushes that effect between the two listeners: the old one is removed before its turn and the new one is not called for an event already in dispatch. Both dialogs now register once and read the latest onCancel from a ref; Dialog refocuses its primary when the question changes under it, which the re-running effect used to do by accident. updateGuard: 10 of 10 afterwards. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Maxaubert
force-pushed
the
feat/28-update-window
branch
from
September 20, 2026 11:20
1c15125 to
06725ce
Compare
github-actions Bot
pushed a commit
that referenced
this pull request
Sep 20, 2026
…tall; built once in core (#28) (#30) * feat(update): the update chip opens a window with the notes, Cancel and Install; built once in core (#28) The chip used to download and quit on a click. It now opens a window that lists what changed, as PLAIN TEXT parsed from the release body (never HTML, never markdown, never a link), with Cancel and Install. Built in core/ so both apps show the same thing (owner: "yes keep the core"), which widens the core from "the terminal" to "what the two apps share"; core 0.2.0 -> 0.3.0. - core: updateTypes, releaseNotes (parser), updatePreview (--preview-update fake: no network, no file, no process, no quit), updateFlow + useUpdateFlow, UpdateChip + UpdateDialog (props only). - The chip never changes width: every label is laid out in one cell. It did change before (104.8 / 54.8 / 96.0 px, measured). - The label ink follows what is behind it (a clipped second copy), after the e2e screenshot showed white "35%" on pale blue on a light theme. - Install asks first while an agent is working; nothing in this app did. A failed download now says so under the chip. - e2e: updateWindow, updateGuard, updateNotes, updateQuiet. App 0.4.0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test(e2e): a second launch with --preview-update previews in the running app (#28) The app is single-instance and usually already running when somebody tries the flag, so that launch hands its command line over and ends. updateQuiet now proves the running app honours it, opens no tab for it, and still never asks GitHub. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(update): the update window gives way to a close question, and its Tab trap leaves Ctrl+Tab alone (#28) Review of #28. The app's chords still work over the update window, so Ctrl+W on a tab that hosts an agent (or Alt+F4) raised the close question UNDER it: same z-index, earlier in the document, with the focus on "Close tab" where nobody could see it. Enter, aimed at Install, ended the agent. App now cancels the update window whenever a question appears, and updateGuard holds it (mutation-checked: with the effect off the new check fails). The dialog's Tab trap also handled Ctrl+Tab, which the app's own capture listener had already used to step the tab strip: one press did both. core/README.md gains the host rule for Prism's half. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(core): a title that names a handle is not an author tail, and wrapped prose is one entry (#28) Review of #28. The author tail took ANY word after "in", so a title such as "Mention people by @handle in comments" was cut down to "Mention people". What follows "in" must now be a url, which is what generate-notes writes; a bare "by @user" at the end still goes. And a hand-written paragraph wrapped at a column came out as one bullet per line; consecutive plain lines are one entry now, ended by a blank line, a heading or a bullet. Both apps' newest real release bodies were parsed by hand as a check. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(dialog): Escape is heard once and for all: the key listener no longer re-registers on every render (#28) Found as a flake in the new updateGuard check (about one run in four): Escape did nothing, with the question on screen and focused. Dialog keyed its keydown effect on an inline onCancel, so the listener was removed and re-added on every render of the app. When another window keydown listener sets state during the same Escape, React flushes that effect between the two listeners: the old one is removed before its turn and the new one is not called for an event already in dispatch. Both dialogs now register once and read the latest onCancel from a ref; Dialog refocuses its primary when the question changes under it, which the re-running effect used to do by accident. updateGuard: 10 of 10 afterwards. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Maxaubert
added a commit
that referenced
this pull request
Sep 25, 2026
…2e harness (#71) (#72) Settings: the colour editor saves the whole setup into Custom and is a theme pick (#8, #29); ThemeSwitchAsk registers once (#22); a hex field commits only a changed draft (#26); the dictation key is never a key that types or a chord the terminal owns, and a press elsewhere ends the capture (#27); the editor takes the focus, traps Tab and hears Escape, the pencil is its own button (#28); a finished download reads fresh state before picking the model (#30). Release and CI: release.yml tags the built commit (#11); core-release runs from main only (#34), waits for terminal-gate by name and its success (#38), and opens an issue when a core change cannot be released; core-version also requires the core version above the base's (#39). Harness: a time limit per scenario and closeApp (#35), profiles removed after each scenario (#36), the poll's first verdict awaited instead of 6 s (#37). Core 0.15.3, app 0.18.3. New reviewSettings e2e and unit tests. Claude-Session: https://claude.ai/code/session_01LJbePcRzre7AusNzPNS2Bk Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
github-actions Bot
pushed a commit
that referenced
this pull request
Sep 25, 2026
…2e harness (#71) (#72) Settings: the colour editor saves the whole setup into Custom and is a theme pick (#8, #29); ThemeSwitchAsk registers once (#22); a hex field commits only a changed draft (#26); the dictation key is never a key that types or a chord the terminal owns, and a press elsewhere ends the capture (#27); the editor takes the focus, traps Tab and hears Escape, the pencil is its own button (#28); a finished download reads fresh state before picking the model (#30). Release and CI: release.yml tags the built commit (#11); core-release runs from main only (#34), waits for terminal-gate by name and its success (#38), and opens an issue when a core change cannot be released; core-version also requires the core version above the base's (#39). Harness: a time limit per scenario and closeApp (#35), profiles removed after each scenario (#36), the poll's first verdict awaited instead of 6 s (#37). Core 0.15.3, app 0.18.3. New reviewSettings e2e and unit tests. Claude-Session: https://claude.ai/code/session_01LJbePcRzre7AusNzPNS2Bk Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #28. Stacked on #29 (
feat/27-verbs-edges), which is this PR's base: merge #29 first, then retarget this one tomain.Owner, 2026-09-19: "when you click the Update badge, it opens like a pop window, which shows the change log or like patch notes for the new update, and then you can choose cancel or install." And: "I would want to see how the Update banner looks in both apps, so if you could enable it and make like a fake update." Decision: "yes keep the core".
What changed
The update chip opens a window; it no longer installs on a click. The window says
Update to <version>, a quietYou have <current>, the notes as a scrollable list, then Cancel and Install (Install is the primary and holds the focus). It closes on Cancel, Escape, a press outside, and Install; progress then shows in the chip.Built once, in
core/(core 0.2.0 -> 0.3.0), so Prism gets the identical thing. This WIDENS the core from "the terminal" to "what the two apps share";core/README.md(contract) andCLAUDE.mdsay so, dated, with the owner's words.core/shared/updateTypes.ts:UpdateInfo { version, url, notes, mock? }.core/shared/releaseNotes.ts: a GitHub release body -> a capped list of PLAIN strings. Drops theby @user in <url>tail, keeps the PR number as text ((#30), taken from the url when the title lacks it), drops New Contributors, Full Changelog, headings, rules, HTML (comments, tags, script/style blocks, tags hidden inside tags), markdown link targets, emphasis marks, control characters and the bidi overrides (U+202E). 20 entries, 160 chars each,+ N more, only the first 20,000 chars of a body are read, and a missing / null / non-string / boilerplate-only body is one line. 37 unit tests, including two that a backtracking regex would hang on.core/main/updatePreview.ts: the--preview-updatefake. No electron, injectable clock.core/renderer/lib/updateFlow.ts(pure reducer, 20 tests) +useUpdateFlow.ts(the hook: the host hands in its bridge and its install guard).core/renderer/components/UpdateChip.tsx+UpdateDialog.tsx: props only,--p-*tokens, the dialog on the opaque--p-side-flat, modelled on this app'sDialog.tsx.The notes are never rendered. Text off the network in a window that can reach the bridge: parsed to strings, printed as React text nodes. No markdown renderer, no
dangerouslySetInnerHTML, no<a>.--preview-update, in the packaged app too and under--e2e: main offers a fake update (next minor, e.g. 0.5.0 from 0.4.0; a realistic generated body) and the real watcher never starts. Install runs about three seconds of fake progress, then the chip saysPreview only: nothing was installedand returns to idle. The dialog also says it is a preview. A second launch with the flag previews in the already-running app (only when no real update is on offer). An unpackaged (dev) build previews unasked; that replaces the old inert mock chip. Under--e2ewithout the flag: no chip, no network, as before.To try it on the installed app after this ships:
"%LOCALAPPDATA%\Programs\PrismTerminal\PrismTerminal.exe" --preview-update.Three things found on the way, all fixed here:
--p-on-accent) for the whole label. I saw it in the e2e's screenshot; no assertion caught it. The fill is now a second copy of the chip in the accent's ink, clipped to the percentage, over a copy in--p-text. Measured in the e2e now.closeAgreed) and its comment said the page settles that first. That was true of Prism's page and never of this app's. App now has an install guard: while an agent is WORKING (holdsWindowClose, the window's own rule) it asks "Stop the agent and install the update?" / "Install and restart" before anything downloads. A preview asks nothing, since it quits nothing. Also: a download that fails now says so under the chip; it used to fall back to "Update" silently.This app's wiring:
latestUpdate()returns the body asnotes(capped),TitleBartakes the core chip, the dialog is mounted once inApp, the app's oldUpdateChip.tsxis deleted. App version 0.3.0 -> 0.4.0.Independent review (2026-09-20), four things found and fixed on this branch
updateGuardholds it (5 new checks), and with the fix taken out the new check fails.core/README.mdcarries the rule for Prism's half. (3020ea4)3020ea4)69dd09f)Dialogkeyed its keydown effect on an inlineonCancel, so the listener was re-registered on every render; when another window keydown listener sets state during the same Escape, React flushes the effect between the two listeners and the question never hears the key. Both dialogs now register once and readonCancelfrom a ref.updateGuardran 10 of 10 afterwards. (1c15125)Checked and left alone: the parser's worst case on a 20,000 character hostile body is about 165 ms (timed on 17 adversarial shapes, none hangs); every
--p-*token and class the two core components use exists in Prism as well;core-v0.3.0-rc.1holds exactly the core of27a2df6(same tree); the diff holds source, docs and the lockfile only; no em-dashes. Known and not changed: an offer that arrives while the window is open replaces its contents (only reachable when a second-launch preview is followed by a real 4-hourly check).After the review: typecheck clean, lint 0 errors and the same 8 warnings, unit 51 files and 687 tests, and every e2e scenario except the two dictation ones, by name:
spawn7,cwdLabel3,indicator9,theme8,newTabFolder6,restore4,handoff3,lastTab7,closeAsk8,links6,dropAndMenu5,options10,edges35,updateWindow52,updateGuard23,updateNotes9,updateQuiet6,exitClosesTab3,promptLayout2, all passing. The packaged build was not rebuilt after the review.Core release candidate
core-v0.3.0-rc.2(commitdd5f51d, cut after the review and holding the core as it is on this branch now;core-v0.3.0-rc.1, commit2ccdc26, is the core before the review and should not be pinned), split from this branch withgit subtree split --prefix=core, tag only;core-distwas not touched and the local split branch was deleted. I type-checked the rc's update files against Prism-work's own TypeScript 5.9.3 and@types/react19.2.17 (read-only, nothing installed there): clean.For Prism's half, a host needs:
notesfrom its own update check,wantsPreview/previewUpdate/runPreviewInstallin main,useUpdateFlow(window.prism, guard),<UpdateChip>in the bar and one<UpdateDialog>at the root. Prism's preload already spellsonUpdate/onUpdateProgress/installUpdatethe same way.closeQuestionTitlegained an'install'target (additive).Tests
updateWindow52,updateGuard18,updateNotes9,updateQuiet6,options10,spawn7,theme8,closeAsk8,edges35,handoff3,lastTab7,restore4.npm run package, thenPT_E2E_PACKAGED=1, drivingdist/win-unpacked; no installer was run):updateWindow52,updateGuard18,updateNotes9,updateQuiet2, all passing. That is the proof the flag works in the packaged app.What the new scenarios prove:
updateWindow: chip in the title bar under--preview-update; a click opens the window and installs nothing; title, "You have", 7 entries as plain text; noby @, no url, no boilerplate, no<a>, only plain elements in the notes; opaque surface; Cancel|Install with Install focused; Escape, Cancel and an outside press each close it; Install runs downloading -> installing -> idle with a rising percentage; the preview line; width and left edge identical across all ~130 samples; afterwards 0 release checks, 0 installs, noprismterminal-update-*folder in temp, no process naming one, the line leaves by itself and the app and its shell are still alive; then the same on a light theme, including the label ink.updateNotes: a hostile 30-line body (<img onerror>,<script>,<iframe>,<a href>, a markdown link) reaches the real page: 0 anchors / images / scripts / frames, the handler never ran, 20 shown and "+ 10 more", the list scrolls, and at the 560x400 minimum window the whole dialog still fits with its buttons.updateGuard: a real-shaped offer whose url the installer refuses before sending a byte (PT_E2E_UPDATE_OFFER, reachable only under--e2e): empty notes say "No notes were published with this release."; with an agent working, Install asks in its own words; Cancel starts nothing; the go-ahead attempts the install, it fails, the chip says so and returns to idle; and closing the window afterwards STILL asks (the pre-answer was reset).updateQuiet: no flag, no chip, watcher off; then a SECOND launch with--preview-updateexits and the running app shows the preview chip, opens no tab for it, and still asks GitHub nothing.Screenshots I opened (
.e2e-shots/)update-dialog-dark.png,update-dialog-light.png: the window centred, title, "You have 0.4.0", a bordered list under "WHAT IS NEW" with accent bullets, the preview line, Cancel and Install. Reads well on both; matches the close question's box, scrim and buttons.update-chip-dark/light.png,update-progress-dark/light.png,update-notice-dark.png: the chip left of the cog; at 35% the filled part is the accent with its ink and the rest is tinted with the theme's text on it (after fix 2); the line "Preview only: nothing was installed" hangs under the chip, right-aligned to it, over the tab strip. In the first notice shot the fill was caught draining backwards over "Update 0.5.0" (a 300 ms transition); the return to idle no longer transitions.update-dialog-empty.png,update-agent-question.png,update-dialog-small.png,update-dialog-long.png,settings-general.png(no chip without the flag; the Version hint fits on one line).For the owner
UpdateDialog.tsx.releaseNotes.ts.Not verified
dist/win-unpackedinstead.updateQuietwas 2 checks, before that check was added, and the check skips itself underPT_E2E_PACKAGED).core-v0.3.0-rc.2) is not in this PR.🤖 Generated with Claude Code