Release 1.7.0: integrate pixel themes, reliability fixes, and desktop branding - #101
Conversation
…e settled Startup could fail with "ERR_ABORTED (-3) loading 'data:text/html...'" when the machine was busy. Since services start while the startup page loads, the application surface could start its navigation while the page was still committing or loading in its own renderer process. The application surface then committed first, and the page's ERR_ABORTED arrived afterwards, while the loadFile promise was still waiting. Electron's loadURL/loadFile promise takes the first main-frame did-fail-load it sees as its own, so the application load rejected with the startup page's abort and startup showed the failure page. Services still start while the page loads. The application surface now waits until the page load has settled, which it usually has by the time services are up. A close during the page load is still a quiet quit, and a real page error on a live window still fails startup. Measured with 40 sequential hidden launches per build, each helper process (renderer, GPU, utility) paused at random for 20 to 300 ms during the first navigations: origin/main failed 4 and 6 of 40, the same loop with this change 0 of 80. The build before "perf(startup): start services while the startup page loads" had 0 of 40. Without pauses all builds start 40 of 40; the median time to a ready window goes from 342 to 379 ms.
Pasting a MiniMax API key into Settings -> Agents -> Provider API keys turned the window black. The key field's onChange read event.currentTarget.value inside the setDrafts((current) => ...) updater. React runs that updater later, during render, whenever an earlier update of the component is still pending (a paste over a character already in the field, the second keystroke of fast typing). By then the event has finished dispatching and currentTarget is null, so the render threw "Cannot read properties of null (reading 'value')" and React unmounted the whole root. The renderer process stayed alive with an empty #root. Key length and shape are not the cause: a 120-character key pasted over a pending edit breaks the same way. The handler now reads the value while the event dispatches and passes it to the updater. No other renderer updater reads an event (checked with an AST scan over src/renderer). Tests: tests/provider-secrets-settings.test.mjs bundles the real component against a React stand-in whose updaters run after dispatch and pastes fake sk-cp-/sk-api- keys of 120 to 10 000 characters, with a trailing newline and space, over a pending edit (fails before this change with the same TypeError); a line-level guard keeps event reads out of state updaters in the renderer.
An error thrown while React renders unmounts the whole root, but the renderer process lives on, so render-process-gone never fires and the main process's crash reload never runs: the window stays black until the app is restarted. That is what the broken provider key field did. The React root now handles onUncaughtError. The first such error logs it with its component stack and reloads the application surface in place; sessions live in the main process and survive the reload, as after a renderer crash. A second one within 30 s (or with no session storage to remember the reload) shows a static page with a Reload button instead of reloading in a loop. Tests: tests/uncaught-error-recovery.test.mjs (reload, cooldown, clock change, no storage, root wiring). Checked in a hidden app: an injected render error reloads to a usable Settings screen, a second one within the cooldown shows the recovery page; forcefullyCrashRenderer still reloads through render-process-gone.
…e settled Startup could fail with "ERR_ABORTED (-3) loading 'data:text/html...'" when the machine was busy. Since services start while the startup page loads, the application surface could start its navigation while the page was still committing or loading in its own renderer process. The application surface then committed first, and the page's ERR_ABORTED arrived afterwards, while the loadFile promise was still waiting. Electron's loadURL/loadFile promise takes the first main-frame did-fail-load it sees as its own, so the application load rejected with the startup page's abort and startup showed the failure page. Services still start while the page loads. The application surface now waits until the page load has settled, which it usually has by the time services are up. A close during the page load is still a quiet quit, and a real page error on a live window still fails startup. Measured with 40 sequential hidden launches per build, each helper process (renderer, GPU, utility) paused at random for 20 to 300 ms during the first navigations: origin/main failed 4 and 6 of 40, the same loop with this change 0 of 80. The build before "perf(startup): start services while the startup page loads" had 0 of 40. Without pauses all builds start 40 of 40; the median time to a ready window goes from 342 to 379 ms.
Pasting a MiniMax API key into Settings -> Agents -> Provider API keys turned the window black. The key field's onChange read event.currentTarget.value inside the setDrafts((current) => ...) updater. React runs that updater later, during render, whenever an earlier update of the component is still pending (a paste over a character already in the field, the second keystroke of fast typing). By then the event has finished dispatching and currentTarget is null, so the render threw "Cannot read properties of null (reading 'value')" and React unmounted the whole root. The renderer process stayed alive with an empty #root. Key length and shape are not the cause: a 120-character key pasted over a pending edit breaks the same way. The handler now reads the value while the event dispatches and passes it to the updater. No other renderer updater reads an event (checked with an AST scan over src/renderer). Tests: tests/provider-secrets-settings.test.mjs bundles the real component against a React stand-in whose updaters run after dispatch and pastes fake sk-cp-/sk-api- keys of 120 to 10 000 characters, with a trailing newline and space, over a pending edit (fails before this change with the same TypeError); a line-level guard keeps event reads out of state updaters in the renderer.
An error thrown while React renders unmounts the whole root, but the renderer process lives on, so render-process-gone never fires and the main process's crash reload never runs: the window stays black until the app is restarted. That is what the broken provider key field did. The React root now handles onUncaughtError. The first such error logs it with its component stack and reloads the application surface in place; sessions live in the main process and survive the reload, as after a renderer crash. A second one within 30 s (or with no session storage to remember the reload) shows a static page with a Reload button instead of reloading in a loop. Tests: tests/uncaught-error-recovery.test.mjs (reload, cooldown, clock change, no storage, root wiring). Checked in a hidden app: an injected render error reloads to a usable Settings screen, a second one within the cooldown shows the recovery page; forcefullyCrashRenderer still reloads through render-process-gone.
…ores and sockets Base protection now treats CanvasTTY's private data as credentials. A shell or file tool call that names the agent-control token or descriptor, the gateways' connection records, the provider and plugin secret stores, account homes, the GitHub sign-in or prepared launch runs (paths taken from the app's own userData folder, passed in by the app), or the control and runtime socket folders under the temporary folder, is refused whatever the program: readers, copies, encoders, sqlite3, recursive walks of the app folder, interpreter one-liners and heredocs, curl --unix-socket, nc -U, socat and Python sockets. The model is told calmly that agents cannot control CanvasTTY this way and to ask for an Orchestrator launch. The project, the app's settings, other sockets, an agent's own account home and the bundled control CLI are unaffected.
…hen close An unauthenticated or malformed request to the control endpoint now gets a stable INVALID_REQUEST whose message says only sessions CanvasTTY launched as orchestrators may use it and how to get one, with no protocol details, token names or paths. An HTTP request line (curl, a browser) gets a minimal 403 with the same text. Either way the connection is closed right after, instead of waiting for the idle timeout; the connection cap and the one-request-per-connection rule are unchanged. Tests cover a guessed NDJSON request, garbage and an HTTP request, and that the token file is 0600 and its folder 0700 even under umask 0 and a pre-existing loose folder.
…review/pr-98-20260929' into dev/test-prs-96-98-20260929
…20260929 # Conflicts: # src/main/services/agent-control/AgentControlGateway.ts # tests/agent-control.test.mjs
|
@howdeploy, could you hold 1.7.0 for a short while? While testing orchestration live on a combined build we found problems that make delegation unreliable, and fixes are in progress on top of #100:
These are not regressions from #100; they also exist on current |
|
@BIackFIame Thanks for the detailed report and for working on the fixes. We'll go ahead with the 1.7.0 release and address these issues in follow-up patches, using community testing of the actual packaged application to confirm and prioritize problems. Please send the fixes as a follow-up PR when they're ready. We'll work through them with the community after release rather than hold 1.7.0 for this batch. |
CanvasTTY 1.7.0
This integrates the reviewed pixel-theme and reliability work into one release branch, including the local fixes found while using the combined build. The application version, package lock, changelogs, and release target are now 1.7.0.
Included work
The 1.7.0 changelogs also collect the features already merged since 1.5.2: orchestration and additional providers, plugin services and launch environments, session restore, terminal and canvas navigation, performance improvements, and security hardening.
Credits
Thank you @teo-nex and @BIackFIame for the upstream PRs integrated here. The integration fixes and release preparation build on your work and retain the original commits.
Thanks as well to @SaneSanders, @cododel, @qSHEMq, @tab11pm, @kootik, and @howdeploy for contributions and integration work included in the 1.7.0 release. The history also credits commits authored as
s079891, without a linked GitHub account.Validation
Local checks passed:
npm run build(including TypeScript checks),npm run audit:secrets, and all 17 tests in the affected settings/summary suites.CI passed on commit
4c7333e: the full test suite, Even G2 tests, TypeScript, production build, browser/MCP smoke checks, Windows pipe-host checks and packaging, and macOS packaging/CLI smoke checks.After merge, tag the merge commit as
v1.7.0and publish the Linux, Windows, and macOS installers and updater manifests through the release workflow.