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
23 changes: 23 additions & 0 deletions .claude/skills/run-mill/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,29 @@ real desktop build has no automatable hook for these:
four visible×focused combinations) — only the real-window wiring
around it is manual-only.

**Manual-only, a different class of gap: `task dev`'s own concurrent-
start guard and per-rebuild reap (docs/goals/BACKLOG.md Standing #8,
`internal/devguard`, `Taskfile.yml`'s `dev:` task, `build/config.yml`'s
`dev_mode.executes`).** `internal/devguard`'s own decision logic (does
a `ps`/`lsof` snapshot show an existing session) is unit-tested
directly (`guard_test.go`) and was verified live against a genuinely
running `task dev` session during this item's own build (correctly
named the real PID and refused). What CI structurally cannot prove:
CI never runs `task dev` itself (no live file watcher, no real Go
recompile-and-relaunch cycle — the exact reasoning
`.claude/rules/testing.md`'s "Dev-loop timing checks" entry already
gives for `BuildIdentityBadge`'s go-stale state), so (a) the
PER-REBUILD reap in `build/config.yml` actually preventing orphan
accumulation across SEVERAL real Go-triggered rebuilds within one live
session, and (b) a genuine second `task dev` terminal invocation
actually refusing to start (not just the guard binary run standalone),
both stay real desktop-mode manual checks: start `task dev`, touch a
watched `.go` file several times in a row and confirm `ps aux | grep
mill.dev.app` never shows more than one instance after each relaunch,
then (in a second terminal, same repo) run `task dev` again and
confirm it exits immediately naming the first session's PID rather
than launching a second window.

Verification for all five stays a real desktop-mode manual check:
launch via `task dev`, set a summon hotkey in Settings, press it from
another app, confirm the panel appears floating/frameless above
Expand Down
26 changes: 25 additions & 1 deletion Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,28 @@ tasks:
its only flags are -config/-port/-nocolour/-s), so there's no
manual override -- the fingerprint IS the mechanism, which is why
not wiping bin/ (above) matters.

A second concurrent `task dev` REFUSES to start (internal/devguard,
docs/goals/BACKLOG.md Standing #8b), naming the already-running
PID instead of silently killing the first session's own vite/app.
build/config.yml's dev_mode.executes also reaps any leftover
mill.dev.app before each Go-rebuild relaunch WITHIN one session
-- the one residual, CI-unprovable gap (a real live wedge/rebuild
timing under an actually-running watcher) is named explicitly in
the manual-only registry (.claude/skills/run-mill/SKILL.md), not
silently assumed fixed.
cmds:
# Concurrent-start guard (docs/goals/BACKLOG.md Standing #8b,
# owner-hit 2026-08-12 evening: THREE concurrent mill.dev.app
# instances, a real crash risk on a 16GB machine). Runs BEFORE the
# destructive sweep below: a second `task dev` invoked while a
# first is already live must REFUSE to start, not silently kill
# the first session's own vite/app the way the sweep would --
# internal/devguard checks for an already-running `wails3 dev`
# process for this repo and exits non-zero naming its PID, which
# aborts this whole task before the sweep steps ever run (Task's
# own default: stop on the first failing cmd).
- go run ./internal/devguard -port {{.VITE_PORT}}
# Defensive orphan sweep (researched root cause, SPEC §3.8): the
# `wails3 dev` supervisor only traps SIGINT+SIGTERM, not SIGHUP, so
# closing the terminal tab (rather than Ctrl-C) kills the supervisor
Expand All @@ -125,7 +146,10 @@ tasks:
# `task dev` has no memory of it and launches a SECOND window. This
# kills any such leftover before starting, so a fresh `task dev`
# always begins from exactly one live instance. `|| true`: a clean
# start (no orphan) is the normal case, not an error.
# start (no orphan) is the normal case, not an error. Only reached
# once the guard above has already confirmed no LIVE `wails3 dev`
# is running, so anything found here is safely presumed a genuine
# orphan, never a second session's own live process.
- pkill -f "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS/{{.APP_NAME}}" || true
# Same failure mode, the vite side (goal 0029, hit live tonight):
# a supervisor killed via SIGHUP can leave the vite dev server
Expand Down
27 changes: 27 additions & 0 deletions build/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,33 @@ dev_mode:
type: blocking
- cmd: wails3 task common:dev:frontend
type: background
# Per-rebuild reap (docs/goals/BACKLOG.md Standing #8a, owner-hit
# 2026-08-12 evening: THREE concurrent mill.dev.app instances,
# traced live to this exact gap). `type: blocking` steps re-run on
# EVERY reload cycle (refresh's own process.ProcessManager.Reload,
# github.com/atterpac/refresh -- confirmed directly against the
# vendored source, not assumed), unlike `background` above (first
# run only). The `primary` step below (wails3 task run) is SUPPOSED
# to have its own previous instance killed automatically by
# refresh's ProcessManager before each restart (a process-group
# SIGKILL) -- but a live-running dev session was directly observed
# with two concurrent mill.dev.app processes from the SAME session,
# one of them orphaned into a foreign process group refresh's own
# tracking never reaped (root cause not fully pinned down: possibly
# a globally-installed `wails3` CLI binary built against a
# different `refresh` version than what this repo's own go.mod
# pins for the app itself). Rather than patch a vendored third-party
# dependency this repo doesn't own, this step is an independent,
# pattern-based reap -- the same `pkill -f` shape Taskfile.yml's own
# dev: task already uses for its start-of-session sweep -- that
# runs regardless of whether refresh's own internal kill succeeds,
# so an orphan can no longer survive past the NEXT rebuild cycle
# even when refresh's own tracking misses it. Matches the exact
# path Taskfile.yml's darwin:run task builds
# (bin/mill.dev.app/Contents/MacOS/mill); `|| true` since the
# common case (nothing stale yet) isn't an error.
- cmd: pkill -f "bin/mill.dev.app/Contents/MacOS/mill" || true
type: blocking
- cmd: wails3 task run
type: primary

Expand Down
16 changes: 16 additions & 0 deletions docs/SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -4213,6 +4213,22 @@ recorded as a real design input (`OPEN`), never silently dropped.
incidents: `task dev`'s start sweep also clears an orphaned vite-port
listener (`lsof -ti :9245`), and a non-blocking pre-start disk check
warns (never blocks) below 2GB free, naming `go clean -cache`.
**Update (2026-08-13, BACKLOG.md Standing #8): the start-of-session
sweep above only ever ran ONCE, at `task dev` startup — orphans could
still accumulate WITHIN one long-running session, one per Go-rebuild
cycle, directly caught live (a running session had two concurrent
`mill.dev.app` processes, one orphaned into a foreign process group
`atterpac/refresh`'s own tracking never reaped — root cause not fully
pinned to one line in a vendored dependency this repo doesn't own).**
Two mechanical fixes: `build/config.yml`'s `dev_mode.executes` gained
a `type: blocking` reap step (`pkill -f` the `.dev.app` binary,
re-runs every reload cycle, confirmed against the vendored engine
source) right before the `primary` run step, backstopping refresh's
own kill regardless of whether it succeeds; and `internal/devguard` (a
real, unit-tested Go package) runs as `Taskfile.yml`'s `dev:` task's
first step, refusing a second concurrent `task dev` by naming the
already-running PID instead of letting the existing sweep silently
kill a genuinely live first session's own vite/app.

## 9.5 Platform kernel & extension contract

Expand Down
2 changes: 1 addition & 1 deletion docs/goals/BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ live-review material, interleaved during owner reviews, not a lane.**
5. [x] Workflow pins/favorites (tech debt, split from goal 0015's remainder 2026-08-12) — DELIVERED 2026-08-13: `pinnedWorkflowIds: string[]` + `togglePinnedWorkflow` on `shared/store.ts`'s existing zustand `persist` (same localStorage tier as `activeWorkTabKey`, goal 0033's precedent — no new Go surface). `app/workflowFrecency.ts`'s new `sortWorkflowsByPinnedAndFrecency` partitions pinned (in pin-order) above the existing frecency-sorted unpinned tail, reusing `sortWorkflowsByFrecency` rather than a second algorithm. A Primer `PinIcon` `IconButton` trailing-visual pin toggle on both the Quick Panel's and ⌘K palette's workflow rows (muted outline unpinned, accent-colored "filled" once pinned) — found and fixed a real Primer interaction bug along the way: `ActionList.Item`'s own `TrailingVisual` wraps children in a `VisualWrap` span with `pointer-events: none` (trailing visuals are decorative-only by the library's own convention), which silently ate every click on the toggle until `pointer-events: auto` was added back on the button itself. Vitest covers the pinned-above-frecency/pin-order/unpinned-id-dropped/no-mutation cases; `quick-panel.spec.ts` gained a full pin→sort→unpin→revert→reload-persists e2e case.
6. [x] ⌘?/⌘/ multi-binding keybinding alias (tech debt, split from goal 0015's remainder 2026-08-12) — DELIVERED 2026-08-13: `Command` grew an optional `extraBindings: KeyCombo[]` alongside `defaultBinding` (`shared/commands.ts`, backward-compatible); `shared/keybinding.ts`'s `keyFromEventCode` gained `/` support (shift-independent, same as every other key — the Shift mod is what distinguishes ⌘/ from ⌘?, both on the physical Slash key). `palette.open` carries both as `extraBindings`, checked against the full registry + `RESERVED_COMBOS` first (no collision — nothing else uses `/`). `dispatchCommandForEvent` checks a command's effective (override-aware) primary plus its extras every dispatch; extras themselves are deliberately NOT override-checked this pass (Settings' recorder-based rebinding UI still edits only the primary). `views/KeyboardShortcutsSection.tsx` renders extras as read-only secondary `KeyComboChip`s next to the primary's click-to-rebind button. Vitest covers dispatch-matches-either-binding + override-doesn't-disable-extras + no-extraBindings-backward-compat; `keymap.spec.ts` gained both a live ⌘//⌘⇧+/ → palette-opens case and a Settings-renders-the-two-read-only-chips case.
7. [ ] [0021 — MCP dogfood gap closure](0021-mcp-dogfood-gap-closure.md) Phase 2: orchestrator-driven live MCP probing against the locked-down-enterprise use cases (the mandate names the orchestrator as the prober — self-driveable, exploratory; produces the next ranked gap list). Phase 3 judgments that need the owner surface as they're found.
8. [ ] Dev-loop instance guards (tech debt, owner-hit 2026-08-12 evening: THREE concurrent `mill.dev.app` instances in the dock, real crash risk on the 16GB machine) — two confirmed root causes, both get mechanical fixes: (a) `wails3 dev`'s Go-rebuild cycle relaunches the app WITHOUT killing the previous instance, so orphans accumulate one per rebuild during heavy agent waves — extend goal 0029's start-sweep into a per-rebuild reap (kill any existing `bin/mill.dev.app` process before the new launch; find the right hook in the Taskfile's dev target or wails3 dev's own lifecycle); (b) `task dev` ran twice concurrently (two backgrounded watchers, each with its own app+vite) — the dev target refuses to start when an instance is already running (vite-port 9245 check + wails3-dev process check, clear message naming the existing PID). This makes the standing never-two-data-sharing-instances rule ENFORCED instead of remembered.
8. [x] Dev-loop instance guards (tech debt, owner-hit 2026-08-12 evening: THREE concurrent `mill.dev.app` instances in the dock, real crash risk on the 16GB machine) — DELIVERED 2026-08-13, both mechanical fixes landed: (a) per-rebuild reap — `build/config.yml`'s `dev_mode.executes` gained a `type: blocking` `pkill -f "bin/mill.dev.app/Contents/MacOS/mill" || true` step right before the `primary` `wails3 task run` step (blocking steps re-run every reload cycle, confirmed directly against the vendored `github.com/atterpac/refresh` engine source — its own `Primary` case already SHOULD kill-then-restart via a process-group SIGKILL, but a live `task dev` session running during this item's own investigation was caught red-handed with two concurrent `mill.dev.app` processes, one orphaned into a foreign process group refresh's own tracking never reaped; root cause not fully pinned to one line since it's in a vendored third-party dependency, so this reap is an independent, pattern-based backstop rather than a patch to code this repo doesn't own — same shape Taskfile.yml's own start-of-session sweep already used); (b) `task dev` now refuses a second concurrent start — `internal/devguard` (a real Go package, `package main`, unit-tested: `guard_test.go` covers process-list parsing, the wails3-dev-process matcher incl. a real false-positive it caught and fixed against a differently-pathed sibling project, port-PID parsing, and message formatting) 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 (+ any port occupancy as corroborating detail) before the destructive sweep steps can run — verified live against a genuinely running session (correctly detected + refused, naming the real 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.
9. [ ] Dock-bounce on parked approvals (small, unlocked by wails beta.6's Flash() gaining macOS support via NSApp requestUserAttention — PR #44's changelog finding; Mill calls Flash nowhere today) — the attention stack (goal 0023/ADR-0032's away-user layer) gains a one-shot dock bounce when an approval parks while the user is away; kernel attention-layer surface per ADR-0035 (same class as the dock badge), NOT a new composition path. Tiny: one call site in the existing NotifyPendingApproval flow + manual-only registry entry (real dock behavior isn't CI-testable).
10. [ ] resizable-table.spec.ts drag-timing flake, PROPER fix (3 confirmed recurrences POST-hardening: PR #24 original, #43's run, #44's run — the expect.poll hardening from PR #33's wave was insufficient) — the parallel-worker drag-timing race needs a structural fix: serialize the spec (test.describe.configure mode serial in its own worker), or replace the synthesized drag with keyboard-based column resize if the component supports it, or a deterministic wait on the drag handle's post-layout geometry. Not another timeout bump — three strikes means the approach changes.
11. [ ] 0030 second-pass linters (gocritic/prealloc/contextcheck/sqlclosecheck — named future work in goal 0028) + `.ls-lint.yml` gains a root `node_modules` ignore (gap found 2026-08-12: a stray root node_modules broke root-file-naming; tiny, rides this or any PR).
Expand Down
Loading
Loading