feat: v0.5.0 header, switcher, phone key bar, shared polling, Matrix "needs you"
build-deb / Lint, test, package (push) Successful in 2m12s
build-deb / Deploy to CT 126 (push) Successful in 3s
build-deb / Notify on failure (push) Skipped

- Header: no SSO chip ("Sign in again" only on expiry), state dot on the
  session chip, LIVE as a dot, ⋯ menu for text size/notifications/fullscreen,
  Panels badge for waiting sessions and alerts.
- Session switcher on the chip; bare URL reattaches the last session; + New
  opens the panel form with project folders.
- Notifications when other sessions start waiting; alerts polled with the
  panels closed; one tab polls for all tabs over BroadcastChannel.
- Sidebar: context bar per session instead of the Context panel; compact
  Repo/CI with "Ask Claude to fix CI"; legible labels.
- Phone: key bar, drawer with backdrop, visualViewport-aware layout, DOM
  renderer on touch, compact connection dot.
- devdesk-notify-matrix: one Matrix message per wait that lasts
  DEVDESK_MATRIX_DELAY seconds with nobody attached.

Closes #8
Closes #13
Closes #14
Closes #15
Closes #16
Closes #17
Closes #28

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-07 23:34:59 -04:00
co-authored by Claude Opus 5.5
parent 7aa624bc13
commit be9b026a01
19 changed files with 838 additions and 257 deletions
+58
View File
@@ -12,6 +12,7 @@ setup() {
export DEVDESK_LIBEXEC="$REPO/libexec"
export DEVDESK_SHARE="$REPO/share"
export DEVDESK_NO_SYSTEMD=1
export DEVDESK_MATRIX_WEBHOOK_FILE="$TMP/no-webhook" # never the real room
export FAKE_LOG="$TMP/claude-args"
export CLAUDE_BIN="$TMP/fake-claude"
export PATH="$REPO/bin:$PATH"
@@ -269,6 +270,63 @@ claude_args() {
[ ! -e "$DEVDESK_STATE_DIR/state/beta.json" ]
}
# Local stand-in for the hookshot webhook: appends each POST body to $TMP/posts.
start_receiver() {
python3 - "$TMP" <<'PY' &
import http.server, sys
tmp = sys.argv[1]
class H(http.server.BaseHTTPRequestHandler):
def do_POST(self):
body = self.rfile.read(int(self.headers['Content-Length']))
with open(f'{tmp}/posts', 'ab') as f:
f.write(body + b'\n')
self.send_response(200); self.end_headers(); self.wfile.write(b'{"ok":true}')
def log_message(self, *a):
pass
srv = http.server.HTTPServer(('127.0.0.1', 0), H)
open(f'{tmp}/port', 'w').write(str(srv.server_port))
srv.serve_forever()
PY
RECEIVER=$!
for _ in $(seq 1 50); do [ -s "$TMP/port" ] && break; sleep 0.1; done
printf 'http://127.0.0.1:%s/webhook/test\n' "$(cat "$TMP/port")" >"$TMP/webhook"
chmod 600 "$TMP/webhook"
export DEVDESK_MATRIX_WEBHOOK_FILE="$TMP/webhook"
}
posts() { [ -f "$TMP/posts" ] && wc -l <"$TMP/posts" || echo 0; }
@test "a wait nobody is attached to is posted to Matrix once (#28)" {
start_server
start_receiver
export DEVDESK_MATRIX_DELAY=1 DEVDESK_PUBLIC_URL=https://dev.example.org
devdesk new alpha
pane=$(tmux -S "$DEVDESK_SOCKET" display -p -t =alpha:shell '#{pane_id}')
hook() { TMUX="$DEVDESK_SOCKET,1,0" TMUX_PANE="$pane" "$REPO/libexec/devdesk-state-hook"; }
echo '{"hook_event_name":"Notification","message":"Claude needs your permission to use Bash"}' | hook
# a second notification in the same wait: no second message
echo '{"hook_event_name":"Notification","message":"Claude is waiting for your input"}' | hook
sleep 2.5
[ "$(posts)" -eq 1 ]
run jq -r '[.session, .url, (.text | test("Claude needs you in alpha"))] | @tsv' "$TMP/posts"
[ "$output" = "$(printf 'alpha\thttps://dev.example.org/?arg=alpha\ttrue')" ]
# answered within the delay: nothing is posted
echo '{"hook_event_name":"PostToolUse"}' | hook
echo '{"hook_event_name":"Notification","message":"Claude needs your permission to use Edit"}' | hook
echo '{"hook_event_name":"PostToolUse"}' | hook
sleep 2
[ "$(posts)" -eq 1 ]
# no webhook file: feature off
rm "$TMP/webhook"
echo '{"hook_event_name":"Notification","message":"again"}' | hook
sleep 2
[ "$(posts)" -eq 1 ]
kill "$RECEIVER"
}
@test "alerts reports unconfigured and unreachable Grafana cleanly" {
run env DEVDESK_GRAFANA_URL= devdesk alerts
[ "$status" -eq 0 ]