Skip to content

feat(desktop): add a desktop app to a project from the apps panel - #60

Merged
winlp4ever merged 7 commits into
epic/electron-targetsfrom
feat/electron-add-app
Oct 10, 2026
Merged

winlp4ever merged 7 commits into
epic/electron-targetsfrom
feat/electron-add-app

Conversation

@winlp4ever

Copy link
Copy Markdown
Contributor

PR 3b-2 of the Electron targets epic (#55), on top of #59 (inspect and trial). The design was reviewed and decided with the user. The add flow was redesigned after three review rounds and reviewed again, then went through three more rounds.

The flow (macOS)

"Add desktop app…" in the Apps panel:

  1. Main shows the picker (Applications) and inspects what was picked.
  2. The window gets a card and a token. It never handles the app's path, and never names a site to allow: the site the trial found is kept in main.
  3. The card shows who stands behind the app: a developer's team, or "not signed by a developer: pinned to this build".
  4. The app is tried confined. A wrapper's site is allowed only as the trial named it, then tried again.
  5. The app is added to project.json, through its changed-on-disk refusal.

Picking an app the project already names (a project from someone else) approves it for this project instead of adding it twice. Its card first shows what the project opens with it: its sites, which the check runs with, and its arguments, which the check doesn't pass.

Approvals (desktop-apps.json in app data)

  • Per copy of an app (where it was picked): a release and a dev build of one bundle id are kept apart.
  • Per project folder (the registry's scope). Each approval covers the app (its developer, or its pinned build) and what the project opens with it. A pull that changes either asks again.
  • Each project records the build it tried.
  • Written to disk before being kept in memory, and checked to read back. A file that doesn't read is set aside and reported, never overwritten.

Statuses are static (opening the panel launches nothing): ready, updated, allow, opens-changed, changed, not-found.

Lifetime

DesktopAdds runs the whole add (main's dialog included) through a host, with one gate before every change and after every wait. An add ends, along with its trial and any inspection under way, when:

  • it's cancelled, or another app is picked;
  • the window closes or reloads;
  • the project switches (the workspace's own switch hook);
  • Kif starts (the add is refused at its next step);
  • the app quits.

An add is done once written: an approval that fails afterwards is said, and the app is never added twice.

Also

  • What a crash left is swept at start.
  • KIFRAME_WORK_DIR gives a test its own work area.

Tests

  • Main's flow with a fake host: every wait driven by deferred promises, the session, Kif's state and the project changed in between. The workspace switch is tested with a real Workspace.
  • The panel's states.
  • An end-to-end test (macOS, run locally): the built app adds a real bundle (the repo's Electron with the fixture as its app, re-signed) through the stubbed picker, tries it confined in its own work area, and writes and approves it.

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

Stated in BACKLOG: Kif starting during a check doesn't end that check; its result is refused afterwards.

On macOS, "Add desktop app…": main shows the picker and inspects what was
picked (the window holds a token, never a path or a site to allow), the
card tries it confined, then it's added to project.json and approved.

Approvals (desktop-apps.json in app data, written whole, one that doesn't
read set aside) are per Mac and per project, and cover the app (its
developer, or the exact build of an unsigned one) and what the project
opens with it. Each desktop app's status shows in the panel, checked
statically (nothing launches). Picking an app the project already names
allows it here. Removing it drops this project's approval; a trial ends
with its add, the project or a quit; leftovers are swept at the start.
- an app a project already names: its card shows what the project opens
  with it (sites, arguments: the check runs with the sites), and an add
  after the project changed what it opens is refused; a site allowed in
  its check is written to the project and approved
- one pick and one add at a time; a check of a card given up never shows
  on another; the add dropped with the window or a new project, a check
  refused once its project closed
- statuses read together; the set-aside note said once; a status that
  can't be read said
…ach wait

- an add lives only while its project is the open one, checked at every
  step and after every wait (a project changed by any path ends it): an
  add given up while its app was looked at is never written
- a finished add tells the open project's agent only; a site allowed in a
  check is kept as such (never guessed), one the project refuses is said
- one app picker at a time (main); the panel never says an add given up,
  and picks nothing while one runs
- the approvals' set-aside note given once by the store itself
Redesign after three review rounds on its lifetime and guards (reviewed):
DesktopAdds runs the whole add (main's dialog included) through a host
(the open project, its session, Kif's state, the picker, the approval),
with one gate before every change and after every wait: a pick cancelled
or a project changed while the user picked or the app was looked at, Kif
started meanwhile, an add during a check: refused, nothing written.

- the workspace tells listeners as it switches project (every add ends,
  its trial too); a window reloading drops its add
- an add is done once written (an approval failing then: said, never
  added twice); only the project's rules are reworded (a file changed on
  disk says to reopen it)
…uild tried

- approvals per copy of an app (its place): a release and a dev build of
  one bundle id never take each other's approval; a project uses one
- the build that was tried is approved (one updated since: "updated")
- a status says what needs no look at the app before looking (another
  project's, opens changed); a pick given up stops looking at its app
- an approval is written to disk before it's kept; one check at a time in
  the panel; one rule for the same app, one "apps changed" in main
… back

- the build a project tried is kept per project (another's approval of an
  update never changes its "updated" or "ready")
- approvals validated before they're written, names cut and copies capped
  (the oldest let go): never a file set aside at the next start
- an add given up stops its look at the app; the switch wiring tested as
  main has it; a check per card in the panel; neutral "not allowed in
  this project yet" wording
…celled add

- a version compares as it's kept (a long one never "updated" forever)
- an approval refused for its details is said in words; a long signing
  identifier kept; copies of one path merged
- an add's look at the app keeps its own failure unless it was given up
- a cancelled add frees the panel at once (per card, as a check)
- the copy cap in one place; the unused copy version removed
@codecov

codecov Bot commented Oct 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 68.84615% with 81 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
apps/desktop/src/main/index.ts 0.00% 57 Missing ⚠️
apps/desktop/src/main/desktop-apps.ts 87.81% 4 Missing and 20 partials ⚠️

📢 Thoughts on this report? Let us know!

@winlp4ever
winlp4ever merged commit 6b8ddd7 into epic/electron-targets Oct 10, 2026
3 of 5 checks passed
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