<feed xmlns='http://www.w3.org/2005/Atom'>
<title>dingger.git/menubar/panel.swift, branch master</title>
<subtitle>Watches the AI coding agents in your tmux panes on macOS and tells you which need you, from the menu bar, a floating panel, the status line and notifications; jumps to them, and restores tmux sessions and agents after a crash
</subtitle>
<id>https://git.soarez.net/sz/dingger.git/atom?h=master</id>
<link rel='self' href='https://git.soarez.net/sz/dingger.git/atom?h=master'/>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/dingger.git/'/>
<updated>2026-11-03T11:05:57+00:00</updated>
<entry>
<title>show each pi agent's context use: a meter in the panel and search, text in the menu</title>
<updated>2026-11-03T11:05:57+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-11-03T11:05:57+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/dingger.git/commit/?id=bd1eb9ef9b3b89ff5825eb8bf9b2e978167c9ad0'/>
<id>urn:sha1:bd1eb9ef9b3b89ff5825eb8bf9b2e978167c9ad0</id>
<content type='text'>
</content>
</entry>
<entry>
<title>show what pi agents cost today, this week and this month, counting forked replies once</title>
<updated>2026-11-01T08:22:01+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-11-01T08:22:01+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/dingger.git/commit/?id=8146d1db419e8bdacd75920da6f3761abf724853'/>
<id>urn:sha1:8146d1db419e8bdacd75920da6f3761abf724853</id>
<content type='text'>
</content>
</entry>
<entry>
<title>mark forgotten drafts in pi agents' input boxes, and keep them across a crash or restore</title>
<updated>2026-10-30T16:59:43+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-10-30T16:59:43+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/dingger.git/commit/?id=959f0d46cac1a09d0821da599d8dff54d1147892'/>
<id>urn:sha1:959f0d46cac1a09d0821da599d8dff54d1147892</id>
<content type='text'>
</content>
</entry>
<entry>
<title>mark as unread: make an idle agent done again, to come back to it</title>
<updated>2026-10-27T12:12:28+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-10-27T12:12:28+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/dingger.git/commit/?id=50c433e82c368b9861a89deecb74e5f29fa50353'/>
<id>urn:sha1:50c433e82c368b9861a89deecb74e5f29fa50353</id>
<content type='text'>
like "mark as unread" in mail: an agent you have seen (idle) becomes done
again, last among done agents of its importance, with no sound or
notification. it stays done until you come back to it: leave its tmux
window and return, or jump to it. marked while you look at it, it holds
until you look away (`held`); a new turn clears it. only idle agents can
be marked.

- daemon: `pane.mark_unread` on the hook socket; the watcher applies it
  after the tick's observations (mark_unread, advance's hold rule).
- `dingger unread [--pane]`: refuses done, working, blocked, and panes
  without an agent; returns once the agent shows as done.
- pi: /unread (refused while the agent works or is blocked).
- panel: right-click an idle agent in the full list, "Mark as unread".
- search: ⌘U or right-click marks the selected idle agent; the footer
  offers ⌘U when it applies; search stays open.

tests: unit (hold, jump, new turn, idle only), e2e step 22 (queue
position, hold, look away and back, jump, refusals, no sound, menu, the
extension), pi-live in real pi 1.4 (a mark, not a turn: an unknown
command would also end done), swift selftest (menu only for idle, footer).
</content>
</entry>
<entry>
<title>panel: a click that moves a point or two is still a click</title>
<updated>2026-10-27T10:15:03+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-10-27T10:15:03+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/dingger.git/commit/?id=a2378a3ed12e62ea34ad9475084ecb5a387386a5'/>
<id>urn:sha1:a2378a3ed12e62ea34ad9475084ecb5a387386a5</id>
<content type='text'>
any pointer movement between press and release started a panel drag and
swallowed the click, so clicking a row often did nothing and had to be
repeated: in a logged session, 12 of 17 clicks were taken as drags. the
5 that counted waited at most 4 ms and ran `dingger focus` in 60-120 ms.
a drag now needs more than 4 points of movement (isDrag, selftest).
</content>
</entry>
<entry>
<title>importance: mark an agent high, normal, or low</title>
<updated>2026-10-26T14:32:18+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-10-26T14:32:18+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/dingger.git/commit/?id=185af91b4d525db6c192a050b53615772fb5fe11'/>
<id>urn:sha1:185af91b4d525db6c192a050b53615772fb5fe11</id>
<content type='text'>
within a status, more important agents are listed first, and so jumped to
first; status stays first. a high agent shows a bold ! before its name, a
low agent's name is lightly dimmed with "low" on its second line, and it
makes no sound (it still notifies and waits in line).

in pi, /importance high|normal|low (completes the levels; none once one is
typed out, so Enter runs it). the extension saves it in the session as a
custom entry (dingger-importance), so /reload, restarts, /resume and
restore bring it back; /new starts at normal. it shows a level other than
normal in pi's footer, emits dingger:importance { level } for other
extensions, and sends it with every report. for other agents, `dingger
importance [level] [--pane]`; it refuses a pi that reports itself, whose
saved level would win.

the daemon stays the one writer of pane state: it writes
@dingger_importance (unset for normal), which goes with the agent and
survives a daemon restart. `dingger status` marks high and low.

tests: e2e step 21 (order, the option, no sound for low, the menu bar,
the command, the extension through a harness that now matches pi's
extension API), a DINGGER_SOUND seam so sounds are observable, and a
status-line check that read the counts once (flaky) now polls.
pi-live: /importance in real pi 1.4, its footer and session entry, and the
level after a restore; every pi there now uses the test's agent directory
(a restored pi read the user's). negative controls fail each part.
</content>
</entry>
<entry>
<title>restore, step 3: offer it in the panel, menu, and status line</title>
<updated>2026-10-23T10:59:59+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-10-23T10:59:59+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/dingger.git/commit/?id=9c9c9dad93b16781ec2fc40bd7582db62240171c'/>
<id>urn:sha1:9c9c9dad93b16781ec2fc40bd7582db62240171c</id>
<content type='text'>
when tmux stops (and its sessions are missing from the running server, or
no server runs), the daemon publishes the restore offer: offer.json for the
menu bar app and @dingger_restore for the status line, withdrawn once
restored or dismissed.

- panel: a first row "↺ Restore 9 sessions · 22 agents" with when it was
  saved and how many agents were mid-turn; clicking restores, "Not now"
  dismisses. an offer shows the panel like a waiting agent; off stays off.
- menu: the offer on top; restore all, one session, or not now.
- status line: a clickable blue ↺N before the clock.
- restore runs through the user's login shell, as a terminal starts tmux,
  so restored panes get a real environment, not launchd's; --report writes
  restore.log and notifies a summary. restore --dismiss.
- commands log only warnings by default; SIGPIPE ends them quietly.

e2e: the offer through the real menu bar binary, the summary notification,
the offer clearing, a restore with no tmux running, and not now staying
dismissed when a terminal starts tmux. the second test daemon is started
directly (it leaked through a function's $! before, and dozens of leaked
daemons fought over the test socket and loaded the machine); each run uses
its own socket and checks its daemons stop.
</content>
</entry>
<entry>
<title>agent search: spotlight-style fuzzy finder on ctrl+opt+cmd+shift+u</title>
<updated>2026-10-22T15:01:04+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-10-22T15:01:04+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/dingger.git/commit/?id=f4a7346fd32f906ccc814364462c18afb3f10616'/>
<id>urn:sha1:f4a7346fd32f906ccc814364462c18afb3f10616</id>
<content type='text'>
a centered, non-activating key panel that lists every agent and jumps to
the chosen one through the same dingger focus path as a row click.

- fzf-style matching: each word must match one of name, session, window,
  harness, status, or directory, letters in order; the best alignment wins
  (word starts and consecutive runs score higher), matched title letters
  are highlighted.
- empty query keeps the menu's order, so enter jumps to the top waiting
  agent.
- the selection follows the agent, not the position, as states change;
  the list scrolls to keep it visible.
- keys: arrows, ctrl+n/p, ctrl+j/k, enter, esc, ctrl+u; the shortcut again
  or clicking elsewhere closes it.
- search_hotkey setting; an unedited config file is upgraded. menu item
  "Search Agents...". rows carry the pane's directory.
- --print-search and --snapshot-search debug flags; the panel's background
  and snapshot code are shared.

selftest covers matching, ranking, highlighting, and selection; e2e step
15 checks queries through the real binary (fails when search only looks at
titles). e2e resolves relative binary paths, which tmux hooks need.
</content>
</entry>
<entry>
<title>panel: on/off is persistent; what it shows is a separate setting</title>
<updated>2026-10-21T21:02:16+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-10-21T21:02:16+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/dingger.git/commit/?id=7649396bf30a65096fc3944e1404835e661f00af'/>
<id>urn:sha1:7649396bf30a65096fc3944e1404835e661f00af</id>
<content type='text'>
hiding the panel with the shortcut used to last only until another agent
started waiting: one action meant both "not now" and "off", with an
invisible override on top of a three-way mode. now there are two visible
settings: the panel is on or off (ctrl+opt+cmd+e and a checkmarked "Show
Panel" item), and while on it shows always or only when agents are waiting
("Panel Shows"). off stays off. turning it on with nothing to show peeks
for 3 seconds. the old mode setting migrates.

selftest covers the rules; it fails if a waiting agent can show a panel
that is off.
</content>
</entry>
<entry>
<title>fix clicks from the menu bar app: find tmux without homebrew on PATH</title>
<updated>2026-10-20T12:01:18+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-10-20T12:01:18+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/dingger.git/commit/?id=aa4d6fcd9eac0d0fe051d6307ed6009adefbb2e1'/>
<id>urn:sha1:aa4d6fcd9eac0d0fe051d6307ed6009adefbb2e1</id>
<content type='text'>
launchd starts the menu bar app with PATH=/usr/bin:/bin:/usr/sbin:/sbin,
so the `dingger focus` it runs for every click (panel rows, menu rows, the
jump shortcut) could not find homebrew's tmux and failed with a misleading
"no tmux client attached"; the app discarded the error. dingger now finds
tmux on PATH or in the usual install locations (DINGGER_TMUX overrides),
reports a missing tmux as such, and the app logs failed jumps.

also: panel rows own every click inside them (labels took the clicks and
counted as window-drag background).

e2e step 14 repeats the menu bar row click and dingger focus under
launchd's environment; both fail with the previous binaries.
</content>
</entry>
</feed>
