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
97 changes: 97 additions & 0 deletions .agents/skills/verify-defi/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
---
name: verify-defi
description: Launch, drive and prove behavior of Defi, the macOS scrolling-columns window manager (defi-daemon plus the defi CLI). Use after any change to runtime, layout, overview, focus, parking or config to get evidence from the real daemon, not just unit tests.
---

# Verify Defi

Defi is a per-user macOS daemon (`defi-daemon`) controlled by the `defi` CLI over a Unix socket. There is no web or simulator surface. The real surface is the installed daemon on the user's desktop, driven by `defi` commands and read back through `defi status`, `defi trace` and `defi list-workspaces --json`. Visual behavior needs Computer Use.

Rules that override convenience (from `AGENTS.md`):

- Exactly one `defi-daemon` per user session. Never `open -n`, never start a second binary by hand.
- You cannot run an isolated second instance. The instance lock is per user. Drive the one installed daemon, or stop it and replace it through the scripts below.
- Installation and desktop tests share one per-user desktop reservation. Scripts acquire it and exit 75 when busy.
- Restore the user's workspace and confirm one daemon before you finish.

## Which tier to use

| Change | Tier | Command |
| --- | --- | --- |
| Docs only | none | check the diff |
| Code, tests, build config | local | `python3 script/verify.py local` |
| Daemon wiring, platform integration, desktop-visible | stage + desktop | `python3 script/verify.py local --stage` then `python3 script/verify.py desktop dist/verification/<run> --wait [--filter DesktopE2ETests/<test>]` |
| Animation, focus, parking, hotkeys, Dock/Command-Tab, overview visuals | all of the above plus live drive | sections below |

`verify.py local` never installs and forces `DEFI_E2E=0`. `desktop` installs the exact staged bundle, runs XCTest, and restores the session store. Zero executed or skipped tests count as incomplete, not pass.

## Launch

The installed app is the unit under test. To put your change into it:

```sh
python3 script/verify.py local --stage # prints dist/verification/<run>
python3 script/verify.py desktop dist/verification/<run> --wait
```

Signing must keep the installed designated requirement or macOS re-prompts for Accessibility and Screen Recording. If the identity is missing, set `DEFI_CODESIGN_IDENTITY` in the ignored `.env.local`. Do not choose another certificate.

Restart the installed build with `defi service restart`. Ready means `defi status` prints `running session=active` and `pgrep -x defi-daemon | wc -l` is 1.

## Doctor

```sh
.agents/skills/verify-defi/doctor.sh
```

Read-only. Prints daemon count, binary path and start time, session state, overview permission, preview counters, and which `defi` is first on `PATH`. Run it first, and again whenever behavior looks wrong. A daemon that has been up for many hours and reports `overviewReceivedPreviews=0` after an overview open is a wedged daemon, not a passing one.

## Drive

Commands are plain CLI calls. Useful, real ones:

```sh
defi toggle-overview # open or close the overview
defi focus-column left|right|first|last
defi focus-workspace up|down
defi focus-workspace-name dev # names come from list-workspaces
defi focus-workspace-position 2
defi move-column left|right|first|last
defi cycle-width next|prev
defi --monitor 2 focus-workspace-name web
defi list-workspaces --json
defi status # one line of key=value counters
defi trace # recent timestamped runtime events
```

Note `defi workspace 2` selects a workspace by ID, not by position, so the daemon answers `unknown workspace: 2`. Use `focus-workspace-position` for a position.

Comment thread
greptile-apps[bot] marked this conversation as resolved.
Parse `defi status` by splitting on spaces and grepping `key=`. Do not scrape by column.

For real human timing, hotkeys and mouse behavior, hold the desktop reservation in an interactive shell (`python3 script/desktop_lock.py --wait bash`) and use Computer Use. Synthetic command bursts alone do not certify native focus or animation quality.

## Evidence

Write evidence under `dist/verification/evidence-<yyyymmdd-hhmmss>/` (ignored by git, survives cleanup):

- `before.status`, `after.status` from `defi status`
- `trace.log` from `defi trace` covering the action
- `workspaces-before.json`, `workspaces-after.json` from `list-workspaces --json`
- screenshots from Computer Use for visual claims
- `/usr/bin/log show --last 2m --info --predicate 'subsystem == "com.quentin.defi"'` for logger output. In zsh `log` is a builtin, so call `/usr/bin/log`.

Proof standard: drive the user path (CLI command or real hotkey), capture the state before and after, and check the side effect in `status` counters or `list-workspaces`. A passing unit test is not proof of a desktop-visible change. For anything visual, correlate the screenshot with `status` and `trace`.

## Cleanup

1. Close any overview you opened (`defi toggle-overview` only if `overview=open`).
2. Restore the initial workspace you recorded in `before.status` with `defi focus-workspace-name <name>`.
3. Restore any config file you edited from your backup. Never leave `~/.config/defi/config.toml` changed.
4. Exit the `desktop_lock.py` shell if you opened one.
5. Run the doctor and confirm exactly one daemon.

Keep the evidence directory.

## Feature map

See `features/README.md`. A proof that drives one entry point is incomplete when the map lists others.
15 changes: 15 additions & 0 deletions .agents/skills/verify-defi/doctor.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Read-only health check for the running Defi daemon.
set -u
echo "daemons: $(pgrep -u "$(id -u)" -x defi-daemon | wc -l | tr -d ' ')"
for pid in $(pgrep -u "$(id -u)" -x defi-daemon); do
ps -o pid=,lstart=,etime=,command= -p "$pid"
done
echo "defi on PATH: $(command -v defi || echo missing)"
if ! status="$(defi status 2>&1)"; then
echo "status: unreachable ($status)"
exit 1
fi
echo "$status" | cut -d' ' -f1-3
echo "$status" | tr ' ' '\n' | grep -E '^(session|workspace|overview|overviewPermission|overviewPreviews|overviewReceivedPreviews|overviewPreviewFailures|overviewCaptures|overviewSurface|hotkeys|observationFailures)=' | tr '\n' ' '
echo
10 changes: 10 additions & 0 deletions .agents/skills/verify-defi/features/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Defi feature map

Maintained verification source for `verify-defi`. One file per user-facing feature.

| Feature | File | Primary proof |
| --- | --- | --- |
| Overview with window previews | [overview.md](overview.md) | `overviewReceivedPreviews > 0` and visible captures |
| Scrolling column navigation | [scrolling-navigation.md](scrolling-navigation.md) | focus and scroll converge, no rollback in trace |
| Virtual workspaces and parking | [workspaces.md](workspaces.md) | active workspace changes, inactive windows parked |
| Service lifecycle and single instance | [service-lifecycle.md](service-lifecycle.md) | restart keeps one daemon and the session store |
30 changes: 30 additions & 0 deletions .agents/skills/verify-defi/features/overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Overview with window previews

## Sub-features

- Open and close with `toggle-overview` or the configured hotkey.
- Window previews captured through ScreenCaptureKit, with title and icon.
- Desktop background per monitor.
- Keyboard navigation and window moves inside the overview.
- Experimental surface transitions (`overview.experimental_surface_transitions`).

## How to get to it (user POV)

Press the overview hotkey, or run `defi toggle-overview`. Requires Screen Recording permission and `overview.window_previews = true`.

## Driving it with the defi CLI

```sh
defi toggle-overview; sleep 3
defi status | tr ' ' '\n' | grep -E '^overview(=|Permission|Captures|Previews|ReceivedPreviews|PreviewFailures|FirstPreviewMs)'
defi toggle-overview
```

Pass: `overview=open`, `overviewPermission=granted`, `overviewReceivedPreviews` greater than 0, `overviewFirstPreviewMs` a number. For visuals take a Computer Use screenshot while open and confirm cards show window content and the desktop background, not flat grey.

## Gotchas

- Status is published on change. Read it a few seconds after opening; `overviewCaptures` is in-flight work and returns to 0.
- `overviewReceivedPreviews=0` with `overviewPreviewFailures=0` and no ScreenCaptureKit activity in the log means captures never started, usually a wedged daemon. `defi service restart` recovers it. Check `/usr/bin/log` for `overview-capture` errors.
- The overview can close on its own when focus changes; poll `overview=` rather than assuming it stayed open.
- Another covering test: `DesktopE2ETests/testOverviewShowsBothMonitorsBeforeLoadingPreviews`.
28 changes: 28 additions & 0 deletions .agents/skills/verify-defi/features/scrolling-navigation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Scrolling column navigation

## Sub-features

- Focus left/right/first/last column.
- Move column, cycle and maximize width.
- Speculative, latest-wins animation with position-only writes.

## How to get to it (user POV)

Configured hotkeys, or the CLI.

## Driving it with the defi CLI

```sh
defi status > before.status
for i in 1 2 3; do defi focus-column right; done; defi focus-column left
sleep 1; defi status > after.status; defi trace > trace.log
```

Pass: `focused=` changes as expected, `animating=false`, `drift=0[]`, `droppedFrames=0`, `focusPending=false`. In `trace.log` no repeated corrections or rollbacks after the last command.

## Gotchas

- With one column on the workspace nothing moves; check `list-workspaces --json` window counts first.
- Slow apps (`slowAppDetails`) legitimately settle later. Wait for `axPending=false`.
- Visual smoothness needs Computer Use with human-paced input, not a tight loop.
- Covering tests: `testHorizontalAnimationFrameWritesPositionWithoutSize`, `testRapidNativeFocusKeepsLatestIntent`.
29 changes: 29 additions & 0 deletions .agents/skills/verify-defi/features/service-lifecycle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Service lifecycle and single instance

## Sub-features

- `defi service enable|disable|start|stop|restart|status`.
- Per-user instance lock before the IPC socket.
- Session store persistence across restart.

## How to get to it (user POV)

Login item, or `defi service ...`.

## Driving it with the defi CLI

```sh
defi list-workspaces --json > before.json
defi service restart; sleep 5
pgrep -u "$(id -u)" -x defi-daemon | wc -l
defi list-workspaces --json > after.json
diff <(jq -S .monitors before.json) <(jq -S .monitors after.json) && echo same
```

Pass: one daemon, `status` reachable, workspace identity and order preserved.

## Gotchas

- Use `defi service restart`, never `open -n`.
- Restart interrupts any in-flight animation; do not run it during a Computer Use session you care about.
- Replacing the bundle needs the desktop reservation and an unchanged designated requirement.
30 changes: 30 additions & 0 deletions .agents/skills/verify-defi/features/workspaces.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Virtual workspaces and parking

## Sub-features

- Named persistent workspaces from `[workspaces].names`, dynamic ordinary ones.
- Per-monitor workspace stacks.
- Inactive workspace windows parked offscreen through topology and Accessibility.
- Move column or window to a workspace or monitor.

## How to get to it (user POV)

Workspace hotkeys, the menu bar item, or the CLI.

## Driving it with the defi CLI

```sh
defi list-workspaces --json > workspaces-before.json
defi focus-workspace-name web; sleep 1
defi status | tr ' ' '\n' | grep -E '^(workspace|hidden|visibility|drift)='
defi focus-workspace-name dev # restore the recorded initial workspace
```

Pass: `workspace=` equals the target, `hidden=` equals the parked window count, `drift=0[]`, and the JSON `active` flag moved.

## Gotchas

- Names depend on the user's config; read them from `list-workspaces --json` first.
- `--monitor <n>` is 1-based display index, while `id` in JSON is the monitor ID.
- Always restore the initial workspace.
- Covering tests: `testFocusedWindowRemainsResolvableWhileParkedOffscreen`, `testUnhiddenOnePixelStripAnchorConvergesWithRealWindowFrame`.
25 changes: 23 additions & 2 deletions Sources/DefiMacOS/OverviewController.swift
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,10 @@ public final class OverviewController: NSObject {
private var surfacePreparationPending = false
private var previewTask: Task<Void, Never>?
private var desktopCaptureRetryTask: Task<Void, Never>?
// Each timed-out native capture is abandoned, not cancelled, so retries are bounded per session.
private static let desktopCaptureRetryLimit = 5
private var desktopCaptureRetryCount = 0
private var desktopCaptureRetryGeneration: UInt64 = 0
private var previewCache: [WindowID: NSImage] = [:]
private let rememberedPreviews = OverviewPreviewCache()
private var idlePreviewTask: Task<Void, Never>?
Expand Down Expand Up @@ -1315,7 +1319,10 @@ public final class OverviewController: NSObject {
selection: selection).filter {
!attemptedPreviewWindowIDs.contains($0.windowID)
}
let hasPendingDesktopCapture = panels.keys.contains {
// Panel updates must not bypass the retry cap once desktop captures keep failing.
// Decided here, before the batch task yields, so the retry that was granted still captures.
let includesDesktops = !desktopCaptureRetriesExhausted(generation: sessionGeneration)
let hasPendingDesktopCapture = includesDesktops && panels.keys.contains {
!capturedDesktopMonitorIDs.contains($0)
}
guard overviewCaptureBatchNeeded(
Expand All @@ -1331,13 +1338,15 @@ public final class OverviewController: NSObject {
await Task.yield()
await self?.capturePreviewBatch(
requests,
includesDesktops: includesDesktops,
generation: generation
)
}
}

private func capturePreviewBatch(
_ requests: [OverviewPreviewRequest],
includesDesktops: Bool,
generation: UInt64
) async {
guard windowPreviewsEnabled, isOpen, generation == sessionGeneration,
Expand Down Expand Up @@ -1378,7 +1387,7 @@ public final class OverviewController: NSObject {
return
}

let desktopRequests = desktopCaptureRequests()
let desktopRequests = includesDesktops ? desktopCaptureRequests() : []
let results = await captureOverviewImages(
previews: requests,
desktops: desktopRequests,
Expand Down Expand Up @@ -1481,8 +1490,18 @@ public final class OverviewController: NSObject {
presentationChanged()
}

private func desktopCaptureRetriesExhausted(generation: UInt64) -> Bool {
desktopCaptureRetryGeneration == generation
&& desktopCaptureRetryCount >= Self.desktopCaptureRetryLimit
}

private func scheduleDesktopCaptureRetry(generation: UInt64) {
cancelDesktopCaptureRetry()
if desktopCaptureRetryGeneration != generation {
desktopCaptureRetryGeneration = generation
desktopCaptureRetryCount = 0
}
guard desktopCaptureRetryCount < Self.desktopCaptureRetryLimit else { return }
desktopCaptureRetryTask = Task { @MainActor [weak self] in
do {
try await Task.sleep(for: .seconds(1))
Expand All @@ -1494,7 +1513,9 @@ public final class OverviewController: NSObject {
guard self.windowPreviewsEnabled, self.isOpen,
generation == self.sessionGeneration
else { return }
// Counted after the batch is requested, so the last allowed retry is not blocked by its own count.
self.schedulePreviewsIfNeeded()
self.desktopCaptureRetryCount += 1
}
}

Expand Down
Loading
Loading