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
+31
View File
@@ -0,0 +1,31 @@
// One tab polls for every open DevDesk tab (devdesk#8): whoever fetches the
// session list or the alerts broadcasts the result, and a tab that heard a fresh
// result recently skips its own poll. Each poll costs a ttyd client and a PTY.
const channel = typeof BroadcastChannel === 'function' ? new BroadcastChannel('devdesk') : null;
const heard = new Map();
const handlers = new Map();
channel?.addEventListener('message', (event) => {
const { key, data } = event.data ?? {};
const handler = handlers.get(key);
if (!handler) return;
heard.set(key, Date.now());
handler(data);
});
export function onShared(key, handler) {
handlers.set(key, handler);
}
export function share(key, data) {
try {
channel?.postMessage({ key, data });
} catch {
// Not cloneable or channel closed: the other tabs just poll themselves.
}
}
export function heardWithin(key, ms) {
return Date.now() - (heard.get(key) ?? 0) < ms;
}