Files
Makito 395280a1f6 perf(stage-tamagotchi,electron-screen-capture): reduce IPC work (#3006)
## Summary

The main process sent the cursor position to the main window 60 times
each second, also when the cursor did not move. Each IPC message then
reached two eventa contexts in the renderer: the shared context, and a
context that the screen capture composable created for itself. In a
profile of the desktop main window, IPC message handling took about 4%
of the main thread, where the Stage renders.

- **Cursor.** The screen service sends a position only when it differs
from the last position that it sent. When a renderer starts the loop
again, for example after a reload, the next position is sent even if it
did not change. In the renderer, a consumer that mounts later starts
from the last position.
- **Screen capture.** `useElectronScreenCapture` takes the eventa
context of the window, and the callers pass
`getElectronEventaContext()`. Before, each call created a context, and
its IPC listener stayed for the life of the page. Each later mount of
the vision or screen capture devtools page added one more listener.

## Boundaries and risks

| Invariant or boundary | Failure mode | Protection | Evidence or gap |
| --- | --- | --- | --- |
| Consumers keep the cursor position | A consumer needs a message while
the cursor stays still | The renderer keeps the last position.
Window-relative positions also follow `useElectronWindowBounds`, so a
moved window updates them without a cursor message | Code inspection of
`useElectronRelativeMouse` and `useElectronMouseInElement` |
| A consumer that mounts later gets a position | A component mounts
while the cursor stays still and keeps its initial position |
`useElectronMouse` keeps the last position, and each new consumer starts
from it | `electron-mouse.browser.test.ts` |
| A reloaded renderer gets a position | After a reload, no position
arrives until the cursor moves | `startLoopGetCursorScreenPoint` clears
the last sent position | `screen.test.ts` |
| Screen capture invokes still reach the main process | An invoke on the
shared context does not resolve | `setupElectronScreenCapture` already
runs on the shared context for system audio lip sync | Typecheck and the
ambient light browser test. In a dev run, `checkMacOSPermission` through
the shared context returned `granted` in 24 ms. Not verified at runtime:
ambient light capture, vision, and the screen capture devtools page |

## Verification

```sh
pnpm -F @proj-airi/stage-tamagotchi exec vitest run src/main/services/electron/screen.test.ts
pnpm exec vitest run --config apps/stage-tamagotchi/vitest.config.ts --project browser src/renderer/electron-mouse.browser.test.ts
pnpm -F @proj-airi/stage-tamagotchi exec vitest run <21 test files that import electron-screen-capture, electron-vueuse, or the screen service>
pnpm -F @proj-airi/electron-screen-capture typecheck
pnpm -F @proj-airi/stage-tamagotchi typecheck
pnpm typecheck
pnpm lint
```

- The screen service tests pass, 2 of 2. Without the unchanged-position
check, both fail. Without the reset on loop start, the reload test
fails.
- The late consumer test passes. Without the kept position, the late
consumer reads 0 instead of 10.
- In the run of 22 files, 176 of 178 tests pass. In that run, the
startup test of `app.browser.test.ts` received no provider capability
report, and a test of `home-assistant.browser.test.ts` failed after 21.8
s. Alone, both files pass, 13 of 13, on this branch and on a branch
without this change.
- Typecheck: `electron-screen-capture` and `stage-tamagotchi` pass, and
the root typecheck passes, 55 of 55 tasks.
- Lint: 0 errors. The 659 warnings are the same count as on `main`.
- These checks ran on `99dce26f9`. After the rebase onto `e68f64c8c`,
the screen service and ambient light tests (8 of 8) and the root
typecheck (55 of 55) ran again and passed. Lint of the changed files
reports 0 errors.
- After the rebase onto `6b1cb3b25`, the screen service and ambient
light tests (8 of 8) and the `electron-screen-capture` and
`stage-tamagotchi` typechecks ran again and passed. The two new `main`
commits change only `server/` and the root Vitest projects.
- After the review fix, the late consumer, ambient light, and chat
click-through browser tests pass, 17 of 17. The `electron-vueuse`
typecheck and the root typecheck (56 of 56) pass. Lint of the changed
files reports 0 errors.
- A manual test in the desktop dev app on `60dd98699`, before the review
fix, found no problem.

Runtime, desktop dev, main window, 15 s profile 4 minutes after launch:

| Measurement | Before | After |
| --- | --- | --- |
| IPC message handling (`renderer_init` `onMessage`) | 629 ms (4.1%) |
115 ms (0.8%) |
| eventa contexts that handle each message | 2 | 1 |
| Main window frame rate | 118.5 fps | 120.0 fps |

The cursor did not move during the After profile, so it has no cursor
messages. A moving cursor still sends up to 60 messages each second, to
one context.
2026-10-17 21:32:07 +09:00
..
2026-08-26 20:13:10 +08:00
2026-08-26 20:13:10 +08:00
2026-08-26 20:13:10 +08:00