feat: DevDesk v0.1.0, a persistent browser terminal for Claude Code
ttyd serves a single-file xterm.js UI styled with the LotusGuild TDS (web_template base.css). Sessions live in a dedicated tmux server unit (devdesk-tmux.service), so restarting or upgrading the web unit never stops Claude. Each named session maps to a project directory. A Claude SessionStart hook records the conversation ID, so after a reboot the session resumes that exact conversation. - bin/devdesk: attach / new / ls / end / pick, with an fzf picker - ttyd requires a credential that NPM injects; origin check enabled - debian/: dh 13 with --no-stop-on-upgrade for the tmux unit, bundled sha256-verified ttyd 1.7.7, lintian overrides, man page - CI: lint, test, build, smoke-test, artifact; v* tags publish to the Gitea apt registry and create a release Refs: tinker ticket 887531976 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
// Bell → attention: desktop notification, favicon badge and a title prefix while
|
||||
// the tab is in the background. Claude rings the bell (preferredNotifChannel:
|
||||
// terminal_bell) when it finishes a turn or needs input.
|
||||
|
||||
import { FAVICON, FAVICON_BADGE } from './icon.js';
|
||||
|
||||
let unread = 0;
|
||||
let baseTitle = document.title;
|
||||
|
||||
function attentive() {
|
||||
return !document.hidden && document.hasFocus();
|
||||
}
|
||||
|
||||
function render() {
|
||||
document.title = unread ? `(${unread}) ${baseTitle}` : baseTitle;
|
||||
const link = document.getElementById('favicon');
|
||||
if (link) link.href = unread ? FAVICON_BADGE : FAVICON;
|
||||
}
|
||||
|
||||
export function setBaseTitle(title) {
|
||||
baseTitle = title;
|
||||
render();
|
||||
}
|
||||
|
||||
export function notificationsState() {
|
||||
if (!('Notification' in window)) return 'unsupported';
|
||||
return Notification.permission; // granted | denied | default
|
||||
}
|
||||
|
||||
export async function requestNotifications() {
|
||||
if (notificationsState() !== 'default') return notificationsState();
|
||||
return Notification.requestPermission();
|
||||
}
|
||||
|
||||
export function ring(session, detail) {
|
||||
if (attentive()) return;
|
||||
unread += 1;
|
||||
render();
|
||||
if (notificationsState() === 'granted') {
|
||||
const note = new Notification(`Claude · ${session || 'devdesk'}`, {
|
||||
body: detail || 'Claude needs your attention',
|
||||
tag: `devdesk-${session}`,
|
||||
renotify: true,
|
||||
icon: FAVICON,
|
||||
});
|
||||
note.onclick = () => {
|
||||
window.focus();
|
||||
note.close();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function clear() {
|
||||
if (!unread || !attentive()) return;
|
||||
unread = 0;
|
||||
render();
|
||||
}
|
||||
|
||||
window.addEventListener('focus', clear);
|
||||
document.addEventListener('visibilitychange', clear);
|
||||
Reference in New Issue
Block a user