Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 0 additions & 25 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ node scripts/foreground-track-check.mjs # prove the helper tells a running proc
node scripts/trim-check.mjs # run the shipped trim on a real clip and check where it landed
node scripts/clip-audio-check.mjs # prove muting a track mutes it, through the real ffmpeg
node scripts/compress-check.mjs # squeeze a real recording and read back what landed
node scripts/toast-button-check.mjs # press the overlay card's button and see where the app goes
node scripts/publisher-views-check.mjs # run the real publisher and see what it counts
node scripts/discord-webhook-check.mjs # run the publisher against a fake Discord and read what it sent
node scripts/streamdeck-check.mjs # start the app with the Stream Deck server on and knock on its door
Expand Down Expand Up @@ -577,30 +576,6 @@ clip takes seconds to reach the library. **Off by opting out**, in Settings, Rec
Borderless windowed, the default in most modern games and what OBS display capture wants anyway,
is fine. The Settings text says so.

**The card can be pressed, in exactly one rectangle.** The sweep's card offers *Open in the editor*,
which opens the session already cut. That undoes three of the rules above, narrowly, and each
narrowing is the load-bearing part:

- `setIgnoreMouseEvents(true, { forward: true })`, so clicks still pass through the card to the game
and the page still sees the pointer. The page says when the pointer is over the button and main
turns the ignoring off for exactly that long. Blanket-enabling the mouse would put a window in
front of a game that can swallow a click.
- **`focusable: false` stays.** A non-focusable window still receives mouse events, so nothing here
needs it relaxed, and relaxing it is the one change that could put somebody at their desktop
mid-match.
- A preload, which this window deliberately did not have. `src/preload/toast.ts`, two functions, and
**not** `window.goodbit`: a sandboxed page over somebody's game has no business with the API that
deletes clips. Main holds the route and hands the page an opaque token, so the window cannot
choose what the button does, which is the same rule `deeplink.ts` applies to a `goodbit://` link.

**Preloads are CommonJS, `.cjs`, and both facts are load-bearing.** A sandboxed preload must be
CommonJS or it dies on its first `import`, and `out/` sits under a `"type": "module"` package, so a
`.js` file there is read as ESM whatever is inside it. **Electron reports neither failure**: the
card drew its button, the click did nothing, and `window.goodbitToast` was undefined with nothing in
any log. The path is handed in from `index.ts` for the same class of reason: `clipToast.ts` is a
dynamic import, so it lands in `out/main/chunks/` and its own `import.meta.dirname` is not where it
looks like it is.

Both windows are real, so **`BrowserWindow.getAllWindows()[0]` is no longer the app**, and neither
is Playwright's `firstWindow()`. `tests/e2e/app.ts` and `window-state.spec.ts` pick the window whose
URL is not a `data:` one. Ten tests across six specs failed at once when that was missed.
Expand Down
40 changes: 1 addition & 39 deletions electron.vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,45 +45,7 @@ export default defineConfig({
build: {
rollupOptions: {
external: ['electron'],
/*
* Two bridges, deliberately.
*
* `index` is the whole `window.goodbit` API, for the app's own window.
* `toast` is two functions for the overlay card that sits over
* somebody's game, which had no preload at all until it grew a button.
* Handing that sandboxed page the app's API to use two of its calls
* would be the opposite of what that window is for.
*/
input: {
index: resolve('src/preload/index.ts'),
toast: resolve('src/preload/toast.ts'),
},
/*
* CommonJS, and that is not a preference.
*
* The toast overlay runs with `sandbox: true`, and Electron loads a
* sandboxed preload as CommonJS only: an ESM one throws on its first
* `import` and is dropped **silently**, which is exactly how it
* presented: the card rendered its button and `window.goodbitToast`
* was undefined.
*
* Both entries rather than one, because two formats out of one rollup
* build is not a thing, and CJS is the form that works in a sandboxed
* preload and an unsandboxed one alike. `src/main/index.ts` points at
* `.js` accordingly.
*/
output: {
format: 'cjs',
/*
* `.cjs`, not `.js`.
*
* The root `package.json` says `type: module`, and `out/` sits under
* it, so a `.js` file there is read as ESM whatever is inside it and
* a CommonJS preload dies on its first `require`. The extension is
* the only thing that overrides that.
*/
entryFileNames: '[name].cjs',
},
input: { index: resolve('src/preload/index.ts') },
},
},
},
Expand Down
134 changes: 0 additions & 134 deletions scripts/toast-button-check.mjs

This file was deleted.

19 changes: 1 addition & 18 deletions src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ function createWindow(): BrowserWindow {
height: TITLEBAR_HEIGHT,
},
webPreferences: {
preload: join(import.meta.dirname, '../preload/index.cjs'),
preload: join(import.meta.dirname, '../preload/index.mjs'),
sandbox: false,
// The renderer is ours, but it also renders filenames and notes that came
// off disk; there is no reason for it to reach Node directly.
Expand Down Expand Up @@ -364,23 +364,6 @@ app.whenReady().then(async () => {
});
onSettingsChange(() => void refreshTrayMenu());

/*
* The card's one button, wired to the same opener the tray uses.
*
* Registered here rather than inside the toast, because a toast reaching for
* a window opener on its own is the wrong direction: that module is also
* loaded by the preview in Settings and by benches, neither of which has a
* window to show. `openIn` already handles a closed, a minimised and an
* already-open window.
*/
void (async () => {
const { listenForToastActions } = await import('./services/clipToast.js');
// The path is resolved here rather than in the toast: that module is a
// dynamic import and lands in a chunk, so its own `import.meta.dirname`
// points at `out/main/chunks`. This file is always `out/main/index.js`.
listenForToastActions(openIn, join(import.meta.dirname, '../preload/toast.cjs'));
})();

// A first run with nowhere to look for clips still opens, so the window can
// ask for a folder. Only a configured install starts watching.
if (isConfigured()) {
Expand Down
10 changes: 1 addition & 9 deletions src/main/services/capture/sessionWatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,14 +328,6 @@ async function readClips(clips: Clip[], game: string): Promise<void> {
* was worth drawing because it says why the machine is busy; this half is
* only worth drawing when it resolves into something.
*/
/*
* The clips, so the card can offer to open them.
*
* Every clip in the session, not only the ones something was found in. The
* person asked for the session, and a clip the detector could not read is
* the one most likely to be worth watching by hand: 76 of the 174 clips in
* the reference library hold nothing the screen can name.
*/
if (found > 0) await showSweepFinished(found, clips.length, clips.map((clip) => clip.id));
if (found > 0) await showSweepFinished(found, clips.length);
else dismissClipToast();
}
Loading
Loading