Skip to content

feat(runtime): inspect and try a desktop app before it's added - #59

Merged
winlp4ever merged 10 commits into
epic/electron-targetsfrom
feat/electron-inspect
Oct 9, 2026
Merged

winlp4ever merged 10 commits into
epic/electron-targetsfrom
feat/electron-inspect

Conversation

@winlp4ever

Copy link
Copy Markdown
Contributor

PR 3b-1 of the Electron targets epic (#55): the runtime half of "Add desktop app…". The desktop half (approval store, Apps panel) is 3b-2. The design was reviewed (and revised by its review and two spikes); the launch failure rule and the pinned digest were redesigned after three review rounds and reviewed again.

A fix to #58: apps that allow one copy

Slack, VS Code and Discord take Chromium's single-instance lock. Its socket goes to macOS's per-user temp (/var/folders), which the confinement denies, so they quit at once, even alone. MAC_CHROMIUM_TMPDIR=<sandbox>/tmp puts it in the sandbox (measured: no confinement rule widened).

Inspection (electron-inspect.ts, static: only plutil and codesign run)

Refused, said:

  • an app outside where it can stay (a disk image, a translocated copy, the Trash, inside another app);
  • not Electron (no version gate: Figma reports 13.6.1);
  • an executable that isn't its own file;
  • a Mac App Store build;
  • a developer signature that doesn't hold, or codesign that can't tell.

Kept: the bundle id, name, Electron version, and the signer. That's a developer (team id, checked with codesign --verify --strict -R), or for an ad hoc or unsigned build a digest of the whole bundle:

  • each entry a JSON line under a version tag;
  • links resolved inside the bundle step by step, so a chain leaving it is refused while a dangling one is kept;
  • sizes checked, sockets and devices refused, limits, and a stop honoured.

Tried read-only on the installed apps: minmux (pinned), Slack, VS Code, Figma and Notion all inspect in 0.1–0.6 s.

Trial (electron-trial.ts)

A confined launch with the network as in a run: an updater can't touch the real app, and a wrapper's offline page can't hide its site. Outcomes, read structurally:

  • ok;
  • site: a wrapper's, named even when unreached, from the error page's unreachable URL over CDP;
  • quit;
  • failed, said as is.

It runs only the build that was picked; a build that can't be checked is said as unread.

The launch's one failure rule

Every failure that ends a launch is an ElectronLaunchError with its why, except a stop. The order:

  1. a stop;
  2. not started;
  3. what the app showed (a site, a page never loaded);
  4. a quit;
  5. anything else already said;
  6. Playwright and CDP errors, in a fixed phrase (never their message: it can carry the endpoint), with the detail on stderr only under KIFRAME_ELECTRON_DEBUG.

A main window that never loaded its page is refused as such. An attach that hangs as the first page fails (measured 1 in 5, cause not found) is tried again within the launch's time.

Also

  • The tests' launch hooks (appArgs, readable, allowUnconfined) left the package's launch options.
  • The unused loopback network mode was removed.
  • Bundle ids with underscores are accepted.
  • A macOS CI job runs the desktop-app suite (user decision).

Tests

  • Real bundles built from the repo's Electron (cloned, the fixture as their app, re-signed ad hoc).
  • A fake app announcing a test's debugging endpoint (refused, stalled, quitting).
  • Digest trees (links, forgery, limits, a stop mid-file).

Each fix's test fails with its bug put back.

- inspectDesktopApp: static checks of a picked .app (where it is, Info.plist
  via plutil, Electron's framework, its executable, App Sandbox, codesign);
  its signer kept: a developer's team, or a digest pinning an unsigned build
- trialDesktopApp: a confined launch without the network beyond this
  machine; read as ok, a wrapper's site (named even unreached), a quit
- apps allowing one copy (Slack, VS Code, Discord) launch confined: their
  lock's socket goes to the sandbox's temp (MAC_CHROMIUM_TMPDIR)
- launch errors say why structurally; the tests' launch hooks leave the
  package's launch options
- a macOS CI job runs the desktop-app suite
- a pinned build's digest covers the whole bundle (helpers, frameworks)
- codesign that fails or times out refuses the app, never read as
  unsigned or unsandboxed; empty names fall back
- a trial runs with the network (an updater can't touch the real app):
  a wrapper's offline page no longer hides its site
- a main window that never loaded its own page is refused at launch, said
  as such (a dev server not running included); a hung attach is retried
- one command helper; the macOS CI job installs ffmpeg
- a pinned build's digest covers the whole bundle and refuses a link that
  leads outside it; unreadable, too large or stopped: refused, said
- an attach that never answers is tried again, then said as the launch's
  own error (runs and trials alike); a trial throws Kiframe's own faults
- an error page whose address can't be read is said as an unloaded page
- the docs say what a trial reaches (the network, as a run)
…igest

Redesign after three review rounds on the same two seams (reviewed):
- every failure ending a launch is an ElectronLaunchError with its why,
  by one rule (stop, not started, quit first, said, anything else in a
  fixed phrase, its detail on stderr only when asked); the trial maps
  only its why
- the pinned digest: the path resolved, each entry a JSON line under a
  version, links resolved inside the bundle step by step (a chain leaving
  it refused, one leading nowhere kept), sizes checked, sockets and
  devices refused, a stop honoured per chunk
- the unused loopback network mode and a duplicate branch removed
…ked build

- what a launch already said (a site, a page never loaded) stays said if
  the app then quits; the attach hands its timeout to the one rule
- a pinned build is held by its digest (signatureHolds compares it); a
  trial runs only the build that was picked
- an inspection stops when asked (codesign and plutil killed)
- bundle ids with underscores accepted (schema and inspection)
- one quoting helper; the digest's link steps no longer block
…s such

- only what the app showed (a site, a page never loaded) beats a quit
- a build that can't be checked (codesign timed out, unreadable) is said
  as unread, never as changed; buildHolds says which
- the stop honoured in the middle of one large file
- one bundle id rule, shared with the schema; one quoting helper, named
  for the two languages it serves
What the main window embeds as it opens is read by a loading frame's src
too: its first navigation uncommitted when the launch seals what's the
app's own, it was stopped once it loaded (the macOS runner's flake). The
single-instance test runs where its fix applies (macOS).
@codecov

codecov Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

A dev build that opens DevTools after Kiframe attached showed them in a
window that appears blank first (never filtered as DevTools), and the
guard stopped it, failing the step (the macOS runner's failure, then
reproduced every time with DevTools opened late). The guard leaves
devtools: pages alone, as the window list and emulation already did.
… runner

On the macOS runner a click under DevTools opened mid-run waits for stability until its timeout (a covered window's animation frames throttled); the test's property is DevTools never counted as leaving the app. Clicking with DevTools open is checked on a real app (BACKLOG, PR 5).
…failed back

- a frame still loading toward what isn't the app's when the launch seals
  is stopped then (never landing as a stop in a run's first step)
- a main window whose back failed is blanked (never left showing the site;
  the run says to relaunch, as with no history), the failure's detail logged
- CI logs desktop-app failures' detail (KIFRAME_ELECTRON_DEBUG)
@winlp4ever
winlp4ever merged commit 98e3e9d into epic/electron-targets Oct 9, 2026
3 checks passed
winlp4ever added a commit that referenced this pull request Oct 10, 2026
Since #59 the electron project holds two files and they ran in parallel:
Electron launched while the other file cloned and re-signed it (spawn
ETXTBSY on Linux for the first tests, a splash slower than its wait on
macOS, both in #61's CI). The suite was meant to run alone: its files now
do too.
winlp4ever added a commit that referenced this pull request Oct 10, 2026
…61)

* feat(desktop): launch a project's desktop app only from its approval

The launch main gives the agent (PR 4a, design reviewed): the app's copy
approved for this project and what it opens, inspected once by its status.
An update by the same developer is tried confined first, quietly (one
trial per app and project), and that build approved compare-and-set (the
same copy, developer and opens, the project still open). Anything else is
refused for the user to settle in the Apps panel, never for the agent to
retry.

- a take of a desktop app keeps its build and what it opened (appBuild:
  shown, never in its key, never a reason to record again)
- Kif starting ends an "Add desktop app…" check in progress
- BACKLOG: #60's CI flakes; what an approval doesn't cover (files/)

* fix(desktop): launch only the build that was tried, and only while approved

- a launch re-reads the approval and the project after looking at the app:
  one removed or switched meanwhile is never launched
- one quiet trial per build: an update landing during a trial is never
  launched untried; a stopped launch stops only its own wait
- a quiet trial that didn't work says why (a site, a quit, a failure); an
  approval that can't be written is refused for the user
- Kif starting ends checks in progress only (picked cards kept); a
  launch's stop reaches its look at the app; the take's build shape from
  its schema; status tests assert the app they return

* fix(desktop): one quiet trial per app, ended with the launcher, status asked again

- one quiet trial per app and project (launches meanwhile wait); after it
  a launch asks the app's status again and must find the build just
  approved (an update, a move, a replacement meanwhile: refused)
- the quiet trial ends with the launcher (stopAll: a project switch, a
  quit), never with one launch's stop; a launch already stopped starts none
- the trial approves against the launch's own project; a launch checks the
  approved build is the one it starts
- an add's check ended by Kif starting or a switch is said in words

* refactor(desktop): each launch tries its own update, stopped with it

Simplified after a third review round on the shared quiet trial: each
launch of an updated app tries it itself (stopped by its own stop, or by
the launcher's as it was when the launch began: a project switch, a quit
refused in words), then approves compare-and-set (two launches that both
tried it approve the same build). No trial shared, kept, or joined.

- an add's check ended (Kif started, given up) always said in words
- a folder in files/ gone mid-copy said as a change; the slow-copy tests
  budget the app's start on the slower of two

* test(runtime): accept an interrupt handled before the first step that met it

On a busy CI runner the page loads slower than the late dialog's 300 ms,
so the dialog is up as the steps begin and is handled before "wait"
instead of "go" (seen twice). Handled once, off camera, before the click
it would block, either way: the click works.

* test(runtime): close the interrupt test again

The previous commit's edit dropped the test's closing brace (lint and format failed in CI).

* test: run the desktop-app suite's files one after another

Since #59 the electron project holds two files and they ran in parallel:
Electron launched while the other file cloned and re-signed it (spawn
ETXTBSY on Linux for the first tests, a splash slower than its wait on
macOS, both in #61's CI). The suite was meant to run alone: its files now
do too.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant