Skip to content

Attention escalation: idle-aware presence, floating approval prompt, cross-device forward (goal 0023) - #8

Merged
alicoding merged 1 commit into
mainfrom
goal/0023-attention-escalation
Aug 12, 2026
Merged

Attention escalation: idle-aware presence, floating approval prompt, cross-device forward (goal 0023)#8
alicoding merged 1 commit into
mainfrom
goal/0023-attention-escalation

Conversation

@alicoding

Copy link
Copy Markdown
Owner

Delivers goal 0023 end to end — the ADR-0034 flow's first PR.

  • idle-aware presence gate: internal/adapters/idletime (ioreg HIDIdleTime, zero cgo, research-verified no TCC gate); away = unfocused OR idle ≥ 300s (Settings knob); fixes the observed focused-but-absent notification suppression; errors fail toward notifying
  • floating approval prompt: ADR-0033's second-window mechanism reused — always-on-top Approve/Deny over whatever app is focused when a decision parks while away; never auto-dismisses (Escape is explicit)
  • notification authorization actually requested (was never called — found in the pinned Wails source) + Alerts-style guidance in Settings copy
  • cross-device forward (default off): pending decisions POST through the user's own configured HTTPRequest via the existing Decision-webhook transport tail — reaches an owner at another machine, §1.1-clean

Goal archived; SPEC §3.7 + ADR-0032 Update ride the change. Full local gates green (go -race, lint both tag sets, 178 vitest, 153 e2e).

🤖 Generated with Claude Code

https://claude.ai/code/session_01FYwojT8GdUbYSoggbvEFft

…rompt, alert guidance, cross-device forward (goal 0023)

Closes the twice-observed missed-decision failure ('background-ish
pattern, not user attention pattern'):

- internal/adapters/idletime (ioreg HIDIdleTime shell-out, zero cgo,
  no TCC gate — research-verified; the clipboard.go pattern): presence
  is now idle-aware. SettingsService.isAway is the ONE decision point:
  away = unfocused OR idle >= threshold (default 300s, Teams' own
  away default; a Settings knob). Fixes the focused-but-absent
  suppression bug (document.hasFocus() alone said 'present' for an
  unattended Mac). Idletime errors fail toward notifying.
- Floating approval prompt (the incoming-call/askpass pattern,
  ADR-0033's second-window mechanism reused): a small always-on-top
  window appears over whatever app is focused when a decision parks
  while away — Approve/Deny inline for MCP writes, Open-in-Mill for
  guardrail parks (typed input, never blind approval). Deliberately
  no HideOnFocusLost: a decision prompt never auto-dismisses (the
  VS Code severity rule); Escape is the explicit dismiss.
- notify.Start now actually requests notification authorization
  (never called before — found reading the pinned source); Settings
  copy documents the macOS Alerts-style toggle (Duo's documented ask).
- Cross-device forward (default off): pending decisions optionally
  POST {kind,id,description,createdAt} through the user's OWN
  configured HTTPRequest via the same transport tail Decision
  webhooks use (never a second HTTP client) — the only layer that
  reaches an owner at another machine; §1.1-clean. httptest-covered.
- e2e: approval-prompt.spec.ts (hash-route standalone render +
  approve-executes); mcpTestClient.ts extracted as the shared
  real-MCP-client helper. Window-level behaviors manual-only.

Goal 0023 archived; SPEC §3.7 + ADR-0032 Update in-change. Full gates
green (go -race, lint both tag sets, 178 vitest, 153 e2e).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYwojT8GdUbYSoggbvEFft
@alicoding
alicoding enabled auto-merge August 12, 2026 01:31
@alicoding
alicoding merged commit c7738af into main Aug 12, 2026
16 checks passed
@alicoding
alicoding deleted the goal/0023-attention-escalation branch August 12, 2026 01:36
alicoding added a commit that referenced this pull request Aug 13, 2026
…ld reap (BACKLOG Standing #8) (#52)

Two mechanical fixes for the owner-hit THREE-concurrent-mill.dev.app
incident (real crash risk on a 16GB machine):

(a) Per-rebuild reap: build/config.yml's dev_mode.executes gains a
`type: blocking` pkill step right before the `primary` `wails3 task
run` step. Blocking steps re-run on every reload cycle (confirmed
directly against the vendored github.com/atterpac/refresh engine
source), backstopping refresh's own kill-then-restart regardless of
whether it actually reaps the previous instance -- a live task dev
session was caught, mid-investigation, with two concurrent
mill.dev.app processes, one orphaned into a foreign process group
refresh's own tracking never caught. Root cause not fully pinned to
one line since it's inside a vendored third-party dependency; this
reap is an independent, pattern-based backstop rather than a patch to
code this repo doesn't own.

(b) Concurrent-start guard: internal/devguard (a real Go package,
unit-tested) runs as the first step of Taskfile.yml's `dev:` task,
checks for an already-running `wails3 dev` process for this exact
repo, and exits non-zero naming the conflicting PID before the
existing destructive sweep steps can run -- which would otherwise
silently kill a genuinely live first session's own vite/app instead of
refusing to start. Verified live against a real running session
(correctly detected and refused, naming the actual PID).

Manual-only registry entry added (.claude/skills/run-mill/SKILL.md)
for what CI structurally can't prove: real per-rebuild-orphan
prevention across several live Go-triggered rebuilds, and a genuine
second-terminal task dev invocation actually refusing to start.


Claude-Session: https://claude.ai/code/session_018pkViCNAuZp2vBv2K9AbUh

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
alicoding added a commit that referenced this pull request Aug 13, 2026
…ld reap (BACKLOG Standing #8)

Two mechanical fixes for the owner-hit THREE-concurrent-mill.dev.app
incident (real crash risk on a 16GB machine):

(a) Per-rebuild reap: build/config.yml's dev_mode.executes gains a
`type: blocking` pkill step right before the `primary` `wails3 task
run` step. Blocking steps re-run on every reload cycle (confirmed
directly against the vendored github.com/atterpac/refresh engine
source), backstopping refresh's own kill-then-restart regardless of
whether it actually reaps the previous instance -- a live task dev
session was caught, mid-investigation, with two concurrent
mill.dev.app processes, one orphaned into a foreign process group
refresh's own tracking never caught. Root cause not fully pinned to
one line since it's inside a vendored third-party dependency; this
reap is an independent, pattern-based backstop rather than a patch to
code this repo doesn't own.

(b) Concurrent-start guard: internal/devguard (a real Go package,
unit-tested) runs as the first step of Taskfile.yml's `dev:` task,
checks for an already-running `wails3 dev` process for this exact
repo, and exits non-zero naming the conflicting PID before the
existing destructive sweep steps can run -- which would otherwise
silently kill a genuinely live first session's own vite/app instead of
refusing to start. Verified live against a real running session
(correctly detected and refused, naming the actual PID).

Manual-only registry entry added (.claude/skills/run-mill/SKILL.md)
for what CI structurally can't prove: real per-rebuild-orphan
prevention across several live Go-triggered rebuilds, and a genuine
second-terminal task dev invocation actually refusing to start.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018pkViCNAuZp2vBv2K9AbUh
alicoding added a commit that referenced this pull request Aug 13, 2026
…tion (#80)

Both v0.1.0 release attempts hung the runner in darwin:run: the build
task's closing echo used backticks around 'task package' and
'task run' inside a sh string -- command substitution, so printing the
help message BUILT AND LAUNCHED the real app, which never exits
headless. Plain quotes now. Likely also the source of phantom local
Mill instances after any 'task build' (Standing #8's class).


Claude-Session: https://claude.ai/code/session_01FJ8wStsHyu7XPLTspNjMnQ

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
alicoding added a commit that referenced this pull request Aug 15, 2026
…con (task #8)

Wails3's own SystemTray API (app.SystemTray.New(), confirmed real and
previously entirely unused by reading pkg/application/systemtray.go/
system_tray_manager.go directly -- zero new dependency, same tier of
finding as the earlier updater/window-geometry discoveries) answers
"is Mill running" the way Raycast/Alfred/1Password already do
(docs/SPEC.md §3.7's own earlier research already converged on this: a
persistent menu-bar icon IS the running-indicator pattern, not a
separate status API).

Uses the safer, reversible default this session's own goal named
explicitly: coexists with the dock icon rather than replacing it --
ApplicationShouldTerminateAfterLastWindowClosed stays true, unchanged,
this is not a menu-bar-only redesign. Clicking the tray icon (or its
"Show Mill" menu item) reuses SettingsService.ShowWindow, a small
extraction from the summon hotkey's own existing show/restore/focus
sequence (bindSummon) rather than a second copy of the same three
lines -- one behavior, two triggers now. A "Quit" menu item calls
app.Quit().

Uses build/appicon.png (already existing, 1024x1024 full color) as the
tray image via SetIcon, not SetTemplateIcon -- macOS's monochrome-
template-icon convention needs a dedicated small alpha-only asset Mill
doesn't have; named honestly as a real, minor polish gap rather than
silently reaching for a mismatched asset.

Verified: go vet, golangci-lint (0 issues), go build clean on both
desktop and CGO_ENABLED=0 server-mode tags (SystemTray/Menu/Quit all
resolve regardless of tag, matching how window creation itself is
already unconditional), a real server-mode Playwright smoke run
confirms no startup crash, full Go suite passes with -race, and a new
test (TestShowWindow_NilWindow_DoesNotPanic) covers the nil-window
guard now that ShowWindow has two callers instead of one. Not
independently verified visually on the real macOS menu bar in this
pass -- no screen access from this session; documented honestly in
SPEC.md rather than claimed. Documented in docs/SPEC.md §3.7.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYwojT8GdUbYSoggbvEFft
alicoding added a commit that referenced this pull request Aug 15, 2026
…14)

Real empirical test, not assumed from the tool list: built a real
go build -tags mcp . desktop binary, launched it, connected a genuine
MCP client (the SDK's own client role), and exercised the built-in
server's 16 tools -- app_info, windows_list, window_control, dom_query,
call_bound_method, keyboard_press among them.

The specific open question from task #14's own scoping (raised when
this tool was first surfaced this session): does keyboard_press
trigger a real OS-level global hotkey golang.design/x/hotkey's
listener can see, or only a DOM-scoped KeyboardEvent? Answered
definitively: called call_bound_method to bind a real summon hotkey
(main.SettingsService.AssignSummonHotkey, an actual TCC/Accessibility-
gated OS registration -- succeeded, no error), minimised the window,
then sent keyboard_press with the identical combo. The press reached
and was handled by the real DOM (its own response returned the
genuine rendered page text), but a follow-up app_info call showed the
window's minimised/visible state unchanged -- proving keyboard_press
is DOM-scoped only, does not reach the OS-level listener.

Verdict recorded in .claude/skills/run-mill/SKILL.md (not adopted into
the standing workflow -- a one-off spike, not wired into CI/Lefthook):
this tool closes part of run-mill's own already-documented desktop-
only gap (window/tray state -- SetIcon'd tray from task #8, minimise/
show/focus -- is now agent-drivable via window_control/dom_query/
call_bound_method) but not hotkey-delivery verification specifically,
which still needs the real desktop app checked by hand, exactly as
SKILL.md already said before this spike.

Real bug hit and fixed along the way, not glossed over: the SDK's
call_bound_method tool takes a `name` argument (package.Service.Method),
not `method` as first guessed -- the tool's own error message
("missing required argument: name") caught this immediately rather
than silently no-op'ing.

No permanent code shipped -- the spike client and its throwaway Go
module were built in /tmp and discarded after use, matching this
task's own framing as investigation, not a feature. docs/SPEC.md §3.7
carries a short pointer to the SKILL.md verdict rather than duplicating
it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYwojT8GdUbYSoggbvEFft
alicoding added a commit that referenced this pull request Aug 15, 2026
Attention escalation: idle-aware presence, floating approval prompt, cross-device forward (goal 0023)
alicoding added a commit that referenced this pull request Aug 15, 2026
…ld reap (BACKLOG Standing #8) (#52)

Two mechanical fixes for the owner-hit THREE-concurrent-mill.dev.app
incident (real crash risk on a 16GB machine):

(a) Per-rebuild reap: build/config.yml's dev_mode.executes gains a
`type: blocking` pkill step right before the `primary` `wails3 task
run` step. Blocking steps re-run on every reload cycle (confirmed
directly against the vendored github.com/atterpac/refresh engine
source), backstopping refresh's own kill-then-restart regardless of
whether it actually reaps the previous instance -- a live task dev
session was caught, mid-investigation, with two concurrent
mill.dev.app processes, one orphaned into a foreign process group
refresh's own tracking never caught. Root cause not fully pinned to
one line since it's inside a vendored third-party dependency; this
reap is an independent, pattern-based backstop rather than a patch to
code this repo doesn't own.

(b) Concurrent-start guard: internal/devguard (a real Go package,
unit-tested) runs as the first step of Taskfile.yml's `dev:` task,
checks for an already-running `wails3 dev` process for this exact
repo, and exits non-zero naming the conflicting PID before the
existing destructive sweep steps can run -- which would otherwise
silently kill a genuinely live first session's own vite/app instead of
refusing to start. Verified live against a real running session
(correctly detected and refused, naming the actual PID).

Manual-only registry entry added (.claude/skills/run-mill/SKILL.md)
for what CI structurally can't prove: real per-rebuild-orphan
prevention across several live Go-triggered rebuilds, and a genuine
second-terminal task dev invocation actually refusing to start.


Claude-Session: https://claude.ai/code/session_018pkViCNAuZp2vBv2K9AbUh

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
alicoding added a commit that referenced this pull request Aug 15, 2026
…tion (#80)

Both v0.1.0 release attempts hung the runner in darwin:run: the build
task's closing echo used backticks around 'task package' and
'task run' inside a sh string -- command substitution, so printing the
help message BUILT AND LAUNCHED the real app, which never exits
headless. Plain quotes now. Likely also the source of phantom local
Mill instances after any 'task build' (Standing #8's class).


Claude-Session: https://claude.ai/code/session_01FJ8wStsHyu7XPLTspNjMnQ

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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