You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -1078,8 +1077,6 @@ green in every cell while an option means nothing, which is exactly how
1078
1077
|`hints.vision.rectangle_min_size`| option | ✅ | ❌ | ❌ | rectangle detection has no OCR answer, so it stays macOS-only even where the vision strategy lands; that half is text-only |
1079
1078
|`hints.vision.rectangle_min_aspect`| option | ✅ | ❌ | ❌ | rectangle detection has no OCR answer, so it stays macOS-only even where the vision strategy lands; that half is text-only |
1080
1079
|`hints.vision.rectangle_max_aspect`| option | ✅ | ❌ | ❌ | rectangle detection has no OCR answer, so it stays macOS-only even where the vision strategy lands; that half is text-only |
1081
-
|`recursive_grid.animation.enabled`| option | ✅ | ✅ | ❌ | the Windows overlay backend has no grid transition animation |
1082
-
|`recursive_grid.animation.duration_ms`| option | ✅ | ✅ | ❌ | the Windows overlay backend has no grid transition animation |
1083
1080
|`smooth_cursor.move_mouse_enabled`| option | ✅ | ✅ | ❌ | cursor movement is not animated on Windows |
1084
1081
|`smooth_cursor.steps`| option | ✅ | ✅ | ❌ | cursor movement is not animated on Windows |
1085
1082
|`smooth_cursor.max_duration`| option | ✅ | ✅ | ❌ | cursor movement is not animated on Windows |
@@ -1186,12 +1183,11 @@ working, which is exactly why the build exists.
1186
1183
**Windows**
1187
1184
1188
1185
1. Native notifications — no toast support
1189
-
2. Grid and recursive-grid transition animation — not implemented
1190
-
3. Smooth cursor and smooth scroll animation — not implemented
1191
-
4. Font resolution — alias mapping only, no system font enumeration
1192
-
5.`neru services` — every subcommand returns `CodeNotSupported`, where macOS
1186
+
2. Smooth cursor and smooth scroll animation — not implemented
1187
+
3. Font resolution — alias mapping only, no system font enumeration
1188
+
4.`neru services` — every subcommand returns `CodeNotSupported`, where macOS
1193
1189
installs a launchd agent and Linux a systemd user unit
1194
-
6. IPC endpoint, client side — the daemon's endpoint is scoped to one user on
1190
+
5. IPC endpoint, client side — the daemon's endpoint is scoped to one user on
1195
1191
every platform, but only the Unix client checks that for itself before
1196
1192
connecting. A named pipe carries no ownership a client can read without
1197
1193
opening it, so the Windows CLI trusts the name it derives from its own SID.
Copy file name to clipboardExpand all lines: internal/adapter/overlay/AGENTS.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -3,7 +3,7 @@
3
3
These rules fail silently at runtime, not at compile time; read before editing.
4
4
5
5
-**Threading is platform-asymmetric.** macOS serializes through the Obj-C bridge (`dispatch_async` to the main thread); Linux must serialize itself — Cairo/X11/Wayland calls are not thread-safe (`linux/manager.go`).
6
-
- **A draw may block, and must never be called while the mode handler's lock is held.** Draws dispatch asynchronously on macOS but hold `renderMu` synchronously on Linux; that asymmetry is deliberate, so callers must assume the blocking case. Windows sits between: a draw holds `renderMu` while it queues commands, and `Flush` hands the frame to the overlay UI thread and returns — painting and presenting happen there, and frames that pile up coalesce (`platform/windows/overlay.go`), so nothing on the keyboard hook's thread waits for pixels. The mode handler computes what to draw under its lock and draws after releasing it (`internal/app/modes/AGENTS.md`); the exceptions are the whole hints surface — the update callback (`hintdraw.go`), the theme refresh and the search input, which now go through the port but still draw under the handler lock, and whose first draw per activation runs the entire `ShowFrame` transition (resize, show, switch) under it — both grid surfaces, whose activations, redraws and per-keystroke updates all run under it (#1211) — and mode teardown, which hides indicators and clears the frame under it. `h.mu` → `renderMu` is therefore a real edge, and safe only while the reverse never exists: nothing holding `renderMu` may call into the app layer or publish to a subscriber that takes `h.mu`.
6
+
- **A draw may block, and must never be called while the mode handler's lock is held.** Draws dispatch asynchronously on macOS but hold `renderMu` synchronously on Linux; that asymmetry is deliberate, so callers must assume the blocking case. Windows sits between: a draw holds `renderMu` while it queues commands, and `Flush` hands the frame to the overlay UI thread and returns — painting and presenting happen there, and frames that pile up coalesce (`platform/windows/overlay.go`), so nothing on the keyboard hook's thread waits for pixels; the recursive-grid transition there is a goroutine that takes `renderMu` per frame and hands each one to that thread the same way (`windows/transition.go`), and every draw that repaints the surface cancels it under the lock first. The mode handler computes what to draw under its lock and draws after releasing it (`internal/app/modes/AGENTS.md`); the exceptions are the whole hints surface — the update callback (`hintdraw.go`), the theme refresh and the search input, which now go through the port but still draw under the handler lock, and whose first draw per activation runs the entire `ShowFrame` transition (resize, show, switch) under it — both grid surfaces, whose activations, redraws and per-keystroke updates all run under it (#1211) — and mode teardown, which hides indicators and clears the frame under it. `h.mu` → `renderMu` is therefore a real edge, and safe only while the reverse never exists: nothing holding `renderMu` may call into the app layer or publish to a subscriber that takes `h.mu`.
7
7
-**Lock topology is deliberate.** The manager owns `renderMu`, held across synchronous draws; animation goroutines lock it via `sharedOverlay`. The mouse-action indicator owns an independent X11/Wayland connection and must **not** share `renderMu` — it has its own `indicatorMu` / `indicatorRenderMu`. **Canceling an animation is the one thing that happens outside it** (#1490): `cancelAnimation` waits for a goroutine that takes `renderMu` on every frame, so every Linux manager method that stops one cancels *before* it takes the lock — and then re-reads the backend pointer under it, because the gap is where a `Destroy` lands. The corollary is that no repaint inside a backend may reach `clear()`, whose first act is that cancel; the ones that clear a surface they are about to redraw go through the `surfaceClear` primitive instead.
8
8
-**Surface primitives split** (#1177): layout, animation, offsets, and label logic live once on `sharedOverlay`; only buffer management, HiDPI scale, and window lifecycle go behind `overlaySurface`. Shared code never touches cgo — primitives take Go types and own their C marshaling, including CString lifetimes.
9
9
- **The Linux backends' exported methods live on `sharedOverlay`, and the manager's nil check is what makes calling one safe** (#1415, ADR 0010): eighteen of them — every draw plus `Hide`, `Clear`, `ClearRect`, `Flush`, `SetHideUnmatched`, `HideHintSearchInput`, `setOriginOffset`, and the pair grid mode's pointer stand-in travels on, `SetGridPointer` / `forgetGridPointer` (#1463) — are declared once and promoted into `x11Overlay` / `wlrootsOverlay`. Each guards itself with `sharedOverlay.drawable()` — is a surface wired, and does `alive()`, the question `overlaySurface` now declares and each backend answers against its own `raw`, still say the native handle is open — in place of the `o.raw != nil` prologue it used to carry. Only `Show`, `Resize` and `Destroy` stay per-backend, because only those three genuinely differ. A promoted method reached through a **nil** backend pointer panics on the promotion, before any receiver guard inside could run — so every dispatch in `linux/manager.go` nil-checks the pointer first, and reads it once (`cancelBackendAnimation`) rather than twice. Those checks are not an interface nobody wrote; ADR 0010 is why, and deleting one turns a silent no-op into a crash. The `!cgo` twins deliberately keep their methods per-backend: their constructors always return nil, so every body exists precisely to be reached on a nil receiver. `Get()` must never return a typed nil either, or every `!= nil` guard downstream silently passes (`backend_linux.go`).
0 commit comments