Skip to content

Open a site's folder from one menu instead of a remembered editor - #209

Merged
juanmaguitar merged 1 commit into
trunkfrom
juanmaguitar/open-directory-with
Aug 9, 2026
Merged

Open a site's folder from one menu instead of a remembered editor#209
juanmaguitar merged 1 commit into
trunkfrom
juanmaguitar/open-directory-with

Conversation

@juanmaguitar

@juanmaguitar juanmaguitar commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

Why

Opening a site's folder shipped in #158 as a remembered choice: an Open in editor button, a
Show in Finder button, a Change editor button, and a modal picker on first use — four
controls and a modal for one intention. This explores the alternative shape sketched in the issue
thread: one menu that says what it does, listing the applications the folder can be opened in.

The application is what the contributor is actually choosing, and it changes — Finder to look at the
files, an editor to change them. Making it a setting configured once puts the wrong thing in front
of them and then makes them go back to change it.

What changes

One control, on the row under the path it acts on. Open directory in ⌄ opens a menu of the
file manager, every editor detection found, and Other application…. Nothing is remembered: the
application is an argument to editor:open, not a stored preference. That deletes editor:get,
editor:choose, the preferences.editor store entry, the first-run picker modal, and the
stale-remembered-editor failure path — three of the four old buttons go with them.

Deliberately not in it: the "(not installed)" rows from the sketch. Detection in
src/editor-launch.js is a fixed table of absolute install locations, and its own header calls it
"a convenience, not the contract" — it misses JetBrains' versioned install directories and most
Linux packaging. A greyed-out VS Code (not installed) would be a confident lie to anyone whose
install the table does not probe, and #158's standing rule is that nothing here ends at a disabled
button. The menu shows what was found; Other application… is always there, never only as a
fallback.

The security shape changed, and that is the part to review hardest. The renderer now names an
executable, where the path used to come out of the main-side store. So editor:open re-establishes
what it is allowed to launch: matchDetectedEditor re-runs detection and answers with the
detected path, which is what gets spawned — checking one string and launching the caller's string
would not be a check (same reason external-url.js hands the OS its parsed URL). A path detection
did not return is refused as unknown-editor before openSiteInEditor is reached. null still
means the native file dialog, whose result is validated exactly as it was under editor:choose.

The path chip and its copy button are untouched — still the floor under all of it.

How to test this

Platforms: any, but the case-insensitivity of the new check is what differs across them and is
covered by tests that run both branches from one machine (test/editor-launch.test.cjs).

Starting state: the app open on any site, with at least one of VS Code / Cursor / PhpStorm /
Sublime Text / Zed installed in its normal location, and ideally one editor installed somewhere
unusual (a JetBrains Toolbox PhpStorm, or any .app outside /Applications).

  1. Look at the site header. Under the path row there is a single Open directory in ⌄ link. The
    old Open in editor / Show in Finder / Change editor buttons are gone, and the path
    chip and its copy button are unchanged.
  2. Click it. The menu lists Finder (or File Explorer), then each editor found on this
    machine by name, then Other application…. No row is greyed out, and no row claims something
    is "not installed".
  3. Choose Finder. The folder opens in the file manager.
  4. Reopen the menu and choose an editor. That editor opens on the folder. Reopen the menu: it looks
    exactly as it did — nothing was remembered, no "Change editor" appeared, the chosen one is not
    marked.
  5. Choose Other application… and press Escape / Cancel in the dialog. Nothing opens and no
    error appears
    — dismissing a dialog is an answer, not a failure.
  6. Choose Other application… again and point it at the editor installed somewhere the menu did
    not list. It opens, and it is still not remembered.
  7. Choose Other application… and point it at something that is not an application (a .txt, or
    any document). A yellow notice appears under the path saying it is not an application this app
    can open a folder in, with a Choose application… button that reopens the dialog.
  8. Open the ⋯ More menu at the top right. Copy path and Show in Finder are still there;
    Open in editor is not — that lives in the new menu now.

What must not have happened:

  • No remembered editor anywhere: no button that says "Open in Cursor", no "Change editor", no picker
    on first use. If any of those appear, a code path from Open a site's code without leaving the app #158 survived.
  • No dead end. Every menu row does something, and every failure leaves a visible notice with a way
    forward. A row that opens nothing and says nothing is the one outcome this feature may not
    produce.
  • Nothing writes to preferences.editor any more. An existing user's stored editor is left inert in
    settings.json — it must not be read, and their sites and contributor details must be untouched.

Not testable by hand here: the Windows and Linux branches of the launchable-path and
case-sensitivity checks. Those are exercised by injection in test/editor-launch.test.cjs rather
than skipped, and I verified both new checks fail the suite when the logic is removed.

Risks and limitations

  • One extra click per open, forever. The remembered-editor button was one click; this is two.
    That is the trade this shape makes, and it is the main thing to decide before merging.
  • Self-review found 3 [fix here] · 1 [follow-up] — all 3 fixed, 1 deferred. Detail in the
    collapsed block below.
  • The menu re-runs detection on each open rather than caching it, so an editor installed while the
    app is running appears next time. That is ~10–20 stats per open, all async, off the main
    thread's critical path.
  • Not done: keyboard-driven verification of the popover on Windows. The menu is
    Dropdown + MenuGroup/MenuItem from @wordpress/components, the same primitives the sidebar's
    feedback dropdown and the ⋯ menu already use.

Related

Follow-up to #158. Explores the alternative UI proposed for it.


Design decisions and alternatives considered

Greyed-out "not installed" rows — rejected. The sketch showed all five known editors always
listed, disabled when absent. Two problems: it turns the detection table from a shortcut into a
claim about the machine, and the table is knowingly incomplete (its own comment says the picker
covers versioned JetBrains installs "exactly as well"). Someone with PhpStorm installed via the
JetBrains installer would read PHPStorm (not installed) and believe the app. A third option —
dimmed but clickable, opening the dialog prefilled — was also dropped: it makes a row mean two
different things depending on a state the contributor cannot see, and Other application… already
covers it honestly.

Dropping the memory entirely — chosen over "remember, last used on top". Keeping the memory
would have kept editor:get, editor:choose and the stored preference alive to save one click.
Since the menu has to exist anyway, the remembered copy is a second source of truth for the same
answer, plus a failure path (the remembered editor moved) that only exists because something was
remembered. The one click is the price.

Where the menu goes. First on the metadata line beside the created date, then beside the copy
button, and finally on its own row under the path — the folder is what it acts on, so it reads
directly beneath the folder, above everything that acts on the site itself.

matchDetectedEditor returns the candidate, not a boolean. A boolean would let a caller check
one string and spawn another; returning the path detection vouches for makes that mistake
unwritable. It lives in editor-launch.js rather than the handler so the case-sensitivity split has
an injected platform and both branches are testable from one machine — the house pattern.

Review outcome (required — see AGENTS.md)

3 [fix here] · 1 [follow-up] — all 3 fixed, the follow-up deferred.

Fixed:

  1. Tests 🟡 — the new allow-list check read process.platform inline, so neither branch of the
    case-sensitivity comparison could be exercised from one machine, and no test passed a
    case-differing path at all: the suite would have stayed green with normalize deleted. Moved the
    check into editor-launch.js as matchDetectedEditor with an injected platform, and added
    three tests covering the macOS, Windows and Linux branches. Verified both new tests fail when the
    logic is removed.
  2. Security 🔵 — the handler compared normalize(candidate.path) but then passed target, the
    renderer's own string, to openSiteInEditor. On a case-sensitive volume /applications/Cursor.app
    would pass the comparison while being a different filesystem entry. Now the detected path is what
    is spawned.
  3. Architecture 🔵 — the comparison duplicated knownEditorName's normalisation while leaving
    that function without a production caller. Both now live in editor-launch.js; the duplication is
    gone.

Deferred:

  1. Architecture 🔵 [follow-up] — the Other application… branch opens the file dialog before
    sitePath is checked against the registry, so a site forgotten in another window would show a
    file browser and only then refuse. The ordering predates this PR (editor:choose had the same
    dialog with no site in hand at all), and fixing it belongs with the registry-staleness handling
    rather than here.

Verified and explicitly not findings: preferences.editor needs no migration (nothing reads it on
either side of the upgrade, and the surviving writer spreads the existing object, so contributor
provenance is preserved); the second detection sweep per open is not a user-visible cost.

Style notes from the same pass, all applied: comments in settings-store.js, main.js and
index.jsx that still described the remembered editor, the editorChoice variable name for a hook
that no longer holds a choice, and 'unknown-editor' as a bare literal rather than a
REFUSAL_REASONS entry.

Implementation notes
  • src/editor-launch.js — adds matchDetectedEditor(editorPath, { platform, env, exists }) and
    REFUSAL_REASONS.UNKNOWN_EDITOR. Detection, launchability and the spawn are unchanged; nothing
    here consults PATH, at either end, for the reason the file's header gives.
  • src/main.jseditor:get and editor:choose are gone; editor:list drops chosen;
    editor:open takes (sitePath, editorPath | null).
  • src/preload.jsgetEditor and chooseEditor removed; openInEditor takes the second
    argument.
  • src/renderer/index.jsxuseEditorChoice becomes useDetectedEditors (detected + loading, no
    chosen, no remember); the picker modal and the three-button row are deleted; the inline notice
    stays and is now the only place a failure speaks, so it carries Choose application… with it.
  • Tests: test/editor-launch.test.cjs gains the three matchDetectedEditor cases;
    test/ipc-wiring.test.cjs swaps the editor:get/editor:choose wiring tests for the refusal and
    file-dialog paths. 419 pass, lint clean.
Screenshots or recording

The menu, open, on a site with four applications found. Everything the folder can be opened in,
in one list, with Other application… always at the end — no row disabled, nothing claiming to know
what is not installed:

1-menu-open

Closed, the resting state: one link directly under the path it acts on, where three buttons and
a "Change editor" used to be:

2-menu-closed

Both captured from the running app on macOS through webContents.capturePage(), with the menu
opened by a real click rather than a mock.

Replaces the remembered-editor flow from #158 with a single "Open directory in"
menu under the site's path: the file manager, every editor detection found, and
"Other application…". The application is now an argument to the action rather
than a setting configured first, so there is nothing remembered, nothing to
change later, and no first-run picker.

Deliberately not in it: the "(not installed)" rows from the sketch. Detection is
a fixed table of absolute install locations that misses JetBrains' versioned
directories and most Linux packaging, so a greyed-out "VS Code (not installed)"
would be a confident lie on the machines it misses — and #158's rule is that
nothing here ends at a disabled button.

The window names the executable now, where the path used to come out of the
store, so `editor:open` re-establishes the set it is allowed to launch:
`matchDetectedEditor` in editor-launch.js answers with the path detection
vouches for, and that is what gets spawned. Checking one string and launching
another would not be a check.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@juanmaguitar
juanmaguitar merged commit 211d250 into trunk Aug 9, 2026
3 checks passed
juanmaguitar added a commit that referenced this pull request Aug 10, 2026
## Why

On Windows, choosing a detected editor from **Open directory in**
appears to do nothing. No editor window, and no error notice either —
the app reports success. The editor is in fact running: Task Manager
shows Visual Studio Code under *Background processes*, holding ~400 MB,
with no window anywhere.

(Reported against the buttons #158 shipped; #209 has since replaced them
with the menu, and the bug is in the spawn underneath both.)

That is the failure shape this project treats as an architectural bug
rather than a cosmetic one. A Contributor Day newcomer clicks a button,
nothing happens, and there is nothing on screen to diagnose.

## What changes

The root cause is `windowsHide: true` on the editor's spawn — the same
option the app passes to its console children, where it stops Windows
allocating a visible console for every npm and grunt subprocess.

For a GUI application the flag means something else. Alongside
`CREATE_NO_WINDOW` it sets `STARTF_USESHOWWINDOW` with `SW_HIDE` in the
new process's STARTUPINFO, and an application that honors `nCmdShow`
when creating its first window starts invisible. Electron apps do, so VS
Code and Cursor both. The spawn itself succeeds, the child emits
`spawn`, `awaitLaunch` returns ok, and the renderer correctly has
nothing to report.

So the editor spawn drops the flag. Nothing else does: the runners keep
it through `hide-child-windows.js`, which this does not touch. That
patch is applied only inside the four runner child processes and never
in the main process, so nothing re-adds the flag to this call site.

The comment at the call site was also wrong in a way worth correcting
while here — it claimed the options match what main.js uses everywhere
else, but `detached` is unconditional here and conditional in the
runners, for a real reason: the runners are killed as a process group,
and this child is released rather than ever signalled.

## How to test this

**Platform: Windows.** The bug does not exist on macOS or Linux —
`windowsHide` is a no-op there, and macOS goes through `/usr/bin/open`
rather than spawning the editor directly. Buildkite builds a signed
artifact for this branch; check it matches the head commit, since
rebasing onto current trunk invalidated the earlier ones.

**Starting state:** a Windows machine with Visual Studio Code installed
by the user installer, so it lands at `%LOCALAPPDATA%\Programs\Microsoft
VS Code\Code.exe`. Any site in the app will do; it does not need to be
initialized.

1. Open the app, select a site, and open the **Open directory in** menu
under the site path. It lists **Show in Explorer**, then **Visual Studio
Code** — if the editor is not listed, detection did not find it, which
is a different problem from this one.
2. Choose **Visual Studio Code**. It opens **with a visible window**,
showing the site folder.
3. Repeat through **Other application…** and pick `Code.exe` by hand. It
opens with a window too — that path reaches the same spawn, and it is
the one a contributor with an editor detection misses will take.
4. Close VS Code, then close the app entirely, and repeat step 2 from a
fresh launch. The window appears again — the editor is spawned detached,
so this also confirms it still outlives the app rather than being killed
with it.

**What must not have happened:**

- **No console window flashes** at any point during steps 2 and 3. That
is what `windowsHide` was doing correctly for the runners, and the
concern with removing it. Then run **Install npm dependencies** on a
site and watch: still no console flashes there either, since that path
is unaffected.
- **No orphaned editor process.** After step 4, closing VS Code's window
should leave nothing behind in Task Manager under "Visual Studio Code" —
the symptom of this bug was precisely a windowless process sitting
there, so the old failure is easy to recognise.

To watch the bug fail to reproduce, do step 2 on a build from `trunk`
first: the menu item does nothing, and Task Manager shows Visual Studio
Code as a background process with no window.

The regression test is `the editor is not asked to start hidden on
Windows` in `test/editor-launch.test.cjs`. It drives the win32 branch
from any machine by injecting `platform`, the house pattern from
`win-spawn-patch.test.cjs`. I checked it fails on the old code —
`windowsHide: true` was present in the recorded spawn options — and
passes on the new. The existing launch-options assertion was updated in
the same direction rather than left pinning the old behaviour.

## Risks and limitations

Self-review came back **0 [fix here] · 2 [follow-up]**, both filed
rather than deferred silently (#202, #203).

I could not test this by hand myself — I have no Windows machine. The
reproduction and the confirmation of the running-but-windowless process
came from a maintainer's VM; the fix itself has only been verified by
the suite and by reading the Win32 process-creation semantics. A
reviewer on Windows driving the steps above is what would actually close
that gap.

The one behaviour change beyond the fix: a contributor who points the
picker at a *console-mode* editor now gets its console window, where
before it was hidden. That is the wanted behaviour — a terminal editor
with a hidden terminal is the same bug — but it is a change, not a
no-op.

## Related

Fixes #181. Follow-ups filed: #202, #203.

---

<details>
<summary>Design decisions and alternatives considered</summary>

**Why not keep the flag and add `shell: false` + a `start` wrapper, or
go through `shell.openPath`?** Both would work around the flag rather
than remove the thing that was wrong. `shell.openPath` in particular
would hand the folder to whatever the OS has registered for a directory,
which is Explorer — that is the *other* button.

**Why not make it conditional — hide for console editors, show for GUI
ones?** There is no reliable way to ask a `.exe` which subsystem it
targets without reading its PE header, and the app has no business doing
that. The simpler rule is correct: this call site launches the
contributor's editor, and an editor's window is the point of the click.

**Why the flag stays for the runners.** They exist to collect output
that is streamed to the renderer; their console windows are pure noise,
and `hide-child-windows.js` documents why the patch has to reach
grandchildren. Nothing about that reasoning applies to the editor.

</details>

<details>
<summary>Review outcome (required — see AGENTS.md)</summary>

**0 [fix here] · 2 [follow-up]** — both follow-ups filed as issues
rather than fixed here.

The judgement pass ran in a subagent with fresh context, per
`.claude/skills/self-review/SKILL.md`. What it verified rather than
assumed:

- Nothing re-applies `windowsHide` to this spawn. `hideChildWindows()`
is called only in the four runner child processes; the main process
never requires the module. `win-spawn-patch.js` self-applies only under
`WPTK_SPAWN_PATCH=1`, never touches `windowsHide`, and its
`resolveSpawnTarget` returns `null` for a `.exe` anyway.
- No console-flash regression at this call site: `resolveLaunch`
produces `/usr/bin/open` on macOS and the editor `.exe` elsewhere, not
the `cmd.exe`/`grunt.cmd` grandchildren the patch exists for.
- No kill-tree interaction: the child is `unref`'d and never registered
with `killChildTree`, so `detached` here is about outliving the app, not
signalling a group.
- The regression test genuinely fails on the old code.

**Follow-up 1 — #202** (cross-platform, 🔵): the Windows rule in
`.github/instructions/code-review.instructions.md` states `windowsHide:
true` flatly, without the GUI/console distinction that caused this. As
written it will give the same wrong advice to the next GUI spawn someone
adds.

**Follow-up 2 — #203** (tests/architecture, 🔵): `patchChildProcess`
deliberately overrides an explicit `windowsHide: false`, so a future
call to `hideChildWindows()` in the main process would silently
reinstate this bug, and no test would catch it —
`test/editor-launch.test.cjs` injects its own `spawn`, and
`test/runner-wiring.test.cjs` only asserts the runners *do* call the
patch.

One style note from the review was fixed before opening: the comment
claiming parity with main.js's other spawn options, described under
**What changes**.

</details>

<details>
<summary>Screenshots or recording</summary>

Nothing on screen changed inside the app — the menu, its items and the
notice area are untouched. What changes is off-app: whether VS Code's
own window appears after the choice.

The before state is visible in #181: Task Manager filtered to "code",
showing Visual Studio Code under *Background processes (1)* with *Apps
(0)* — the editor running with no window.

</details>

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
juanmaguitar added a commit that referenced this pull request Aug 10, 2026
## Why

Revealing a site's folder in the file manager failed with `Could not
open the folder: unknown error`. The app knew exactly what had gone
wrong — it had refused on purpose — and said the one thing that was not
true.

It happens most visibly while a site is still being created: the clone
takes minutes, the site is not in the registry until it finishes, and
every reveal in that window is refused. That is #180, and the wrong
message is the half a contributor actually meets.

## What changes

The root cause is not the sentence, it is that there were two places
deciding it. `showInFileManager` built its own string out of
`result.error` alone, and a refusal is precisely the case with no
`error` — it carries a `reason` instead. #209 had already written the
vocabulary for the editor menu; the reveal path never asked it.

So the decision moves, not just the words. `noticeForOpenResult` in the
new `src/renderer/open-failure.cjs` answers all three questions the two
callers were answering separately — is there a notice at all, what does
it say, and is "Choose application…" a way out of it — and the window is
left with an assignment.

Three things fall out of moving the decision rather than only the
sentence:

- **`open-failed` gets a branch.** It is the file manager's own refusal
and it carries the OS's message, which the hand-rolled string did
surface. Reusing the editor's vocabulary without adding it would have
been a regression dressed as a cleanup.
- **The picker is offered per reason instead of always.** "Choose
application…" answers "that editor did not work". It is not an answer to
a refused *folder* — `openSiteInEditor` checks the folder before it
looks at the editor, so picking another one returns the identical
sentence — and there is no second file manager to choose.
- **The generic fallback stops naming an application**, since both
callers share it now and only one of them is opening one.

This is PR 1 of 3 on #180 and it deliberately does not make the folder
open; the refusal is still there. It ships first because it stands on
its own: any `openPath` failure, guard or not, currently ends in
"unknown error".

## How to test this

**Platform: any.** Nothing here is platform-specific — the same refusal
and the same notice on macOS, Windows and Linux.

**Starting state:** the app open with at least one site that has
finished setting up.

1. Open the **Open directory in** menu under the site path and choose
the file manager entry (**Show in Finder** / **Show in Explorer**). The
folder opens, and no notice appears. This is the path that must not have
regressed.
2. Quit the app. Move or rename the site's folder on disk, then reopen
the app and repeat step 1. A notice appears explaining that the folder
could not be opened, quoting the reason the OS gave — **not** the words
"unknown error".
3. With the notice showing, confirm there is **no "Choose application…"
button** beside it. Picking an application is not a way out of a folder
that will not open.
4. Now choose an *editor* from the same menu. Its failures still offer
**Choose application…**, because there picking a different one is a real
next step.

**What must not have happened:**

- **No blank notice.** The notice's state changed from a string to `{
message, offerPicker }`; a missed read would render an empty warning box
rather than throw. Any notice you can produce must have visible text in
it.
- **The dialog's Cancel is still silent.** Choose **Other application…**
and press Escape or Cancel. No notice appears — closing a dialog is an
answer, not a failure, and the early return that guaranteed that moved
into the new module.
- **A failed editor is still re-detected.** After an editor fails,
reopening the menu re-runs detection rather than offering the dead entry
as if nothing happened.

To watch the old behaviour, do step 2 on a build from `trunk`: the
notice reads `Could not open the folder: unknown error`.

## Risks and limitations

Self-review came back **1 [fix here] · 2 [follow-up]** on the first pass
and **1 [fix here] · 1 [follow-up]** on the re-review; everything at
`[fix here]` is fixed, and the detail is in the collapsed block below.
The first pass caught something worth stating plainly: my original test
claimed to reproduce #180 and did not, because the sentence was never
the broken part. That is what drove the restructure.

The honest limitation is that **nothing fails if the wire is cut.**
These tests cover the module; reverting the call site in `index.jsx` to
the hand-rolled string would leave the suite green.
`test/ipc-wiring.test.cjs` exists for exactly that class of gap on the
main-process side, but there is no renderer equivalent, and
`setup-steps.cjs`, `terminal-hints.cjs` and `update-plan.cjs` all have
the same unproven wire. This PR inherits the hole rather than digging
it; it is filed as a follow-up rather than solved here.

I could not test the OS-failure branch by hand on Windows — step 2 above
was driven on macOS. The message it quotes comes from `shell.openPath`,
which is Electron's, not ours.

## Related

Part of #180. Follow-ups: #202, #203 are unrelated to this one.

---

<details>
<summary>Design decisions and alternatives considered</summary>

**Why not just add the `reason` branch to `showInFileManager`'s
string?** That is the two-line version, and it leaves two places
deciding the same thing — which is the actual defect. The next reason
added to either guard module would have to be remembered in both.

**Why a `.cjs` module rather than a hook or a helper inside the
component?** It is the house pattern for renderer logic worth testing:
`setup-steps.cjs`, `path-basename.cjs`, `update-plan.cjs`. The renderer
bundle imports it, `node --test` requires it directly, and neither needs
a DOM.

**Why derive `offerPicker` from the reason rather than from the
caller?** The first draft passed `offerPicker: true` from the editor
path and `false` from the reveal path, which reads as correct and is
not: `unregistered-site` and `unavailable` reach the *editor* caller
too, and the picker is a dead end for both. The reason knows; the caller
only thinks it does.

</details>

<details>
<summary>Review outcome (required — see AGENTS.md)</summary>

Two passes, both dispatched to a subagent with fresh context per
`.claude/skills/self-review/SKILL.md`.

**First pass — 1 [fix here] · 2 [follow-up].**

- *[fix here] Tests 🟡* — the "#180 reproduction" test did not fail on
the old code. `describeOpenFailure` already handled `unregistered-site`
on trunk; the bug was at the call site. Reverting only the call site
would have left the whole new suite green with #180 back. **Fixed** by
moving the mapping — not just the sentence — into `noticeForOpenResult`,
which is the restructure this PR is now built around.
- *[follow-up] Architecture 🔵* — `offerPicker` was unconditional on the
editor path. **Fixed**, not deferred: derived from the reason via
`PICKING_HELPS`.
- *[follow-up] Architecture 🔵* — the generic fallback said "in an
application", wrong for the file-manager caller. **Fixed**, not
deferred.

**Re-review — 1 [fix here] · 1 [follow-up].** All three above confirmed
resolved by reading.

- *[fix here] Tests 🔵* — the picker-membership and per-reason lists were
hand-maintained, so a seventh reason would arrive with the generic
sentence and no failure. **Fixed**: both tests now read the reason set
out of `editor-launch.js` and `site-registry.js` (`REFUSAL_REASONS` plus
scanned literals) and fail if a reason is undescribed or unjudged. I
verified the guard bites by adding a fake reason to `site-registry.js` —
two tests fail — then reverting it. The scan also asserts it matched
something, so it cannot go vacuously green.
- *[follow-up] Tests 🔵* — the untested wire, described under Risks
above.

</details>

<details>
<summary>Screenshots or recording</summary>

The only visible change is the text inside the existing notice, and the
absence of the **Choose application…** button beside a file-manager
failure. The notice box, its position and its styling are untouched.

Before: `Could not open the folder: unknown error`, with a **Choose
application…** button that returns the same sentence.

</details>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@juanmaguitar
juanmaguitar deleted the juanmaguitar/open-directory-with branch August 11, 2026 11:29
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