diff --git a/README.md b/README.md index 8038926..c229138 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ The tmux server runs in its own unit, so every Claude process lives in **that** - Configure `DEVDESK_GRAFANA_URL`, plus a Viewer token in `DEVDESK_GRAFANA_TOKEN_FILE`. - **Repo / CI panel:** branch, ahead/behind, uncommitted changes, HEAD's Gitea CI status and open PRs for the session's folder. When CI fails, **Ask Claude to fix CI** types a prompt to read the failed job's log. - **Context:** a Claude `statusLine` command (`devdesk-statusline`) snapshots each session's model, context-window use and cost to `/var/lib/devdesk/context/.json`. The Sessions panel shows it as a bar (cost in the tooltip), and Claude's own footer shows a one-line summary. +- **Light mode:** the terminal switches palettes with the page. DevDesk starts Claude with its `dark-ansi` theme so Claude draws with the terminal's 16 colours, and the light palette maps bright white to ink; tmux and the status line use ANSI colours too. A session picks up Claude's theme the next time Claude starts in it (`/exit`, then ⏎ resumes the same conversation). - **Phone:** a key bar replaces the shortcut hints (Esc, Tab, ⇧Tab, ^C, ↑, ↓, ⏎, Claude/Shell window), and the panels open as a drawer. - **SSH:** `devdesk attach `, `devdesk new [project]`, `devdesk rename `, `devdesk end `, `devdesk ls`, `devdesk pick`, `devdesk repo `, `devdesk alerts`. @@ -59,8 +60,9 @@ The tmux server runs in its own unit, so every Claude process lives in **that** | Ctrl-b d | Detach (the session keeps running) | | Shift+drag | Native browser selection (plain drag goes to tmux copy-mode, which copies via OSC 52) | | Ctrl+Shift+C / V | Copy selection / paste | +| Ctrl+Shift+P | Command palette (also the ⌕ button) | -Text size, desktop notifications (with a mute) and fullscreen are in the **⋯** menu. In Chromium, fullscreen also captures Ctrl+W/Ctrl+T, which browsers otherwise keep for themselves. +The **☀/◐** button switches light and dark mode (saved per browser, shared with the other web_template apps' `lt_theme` preference). Text size, desktop notifications (with a mute), the keyboard shortcuts sheet and fullscreen are in the **⋯** menu; everything is also in the command palette. In Chromium, fullscreen also captures Ctrl+W/Ctrl+T, which browsers otherwise keep for themselves. ## Configuration - **Put your changes in `/etc/devdesk/env.local`.** It's read after the packaged `env` and never shipped, so `apt upgrade` never asks about it. Example: `DEVDESK_REMOTE_CONTROL=1`. diff --git a/bin/devdesk b/bin/devdesk index d5c066f..8eb3965 100755 --- a/bin/devdesk +++ b/bin/devdesk @@ -296,20 +296,24 @@ cmd_attach() { --list) [ $# -eq 1 ] || dd_die "--list takes no arguments" cmd_list + dd_linger return ;; --repo) shift cmd_repo "$@" + dd_linger return ;; --alerts) cmd_alerts + dd_linger return ;; --upload) shift cmd_upload "$@" + dd_linger return ;; --new | --rename | --end) @@ -317,6 +321,7 @@ cmd_attach() { shift "cmd_$action" "$@" ok_json + dd_linger return ;; --existing) diff --git a/debian/changelog b/debian/changelog index 6d92a5b..8b5e2b9 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,29 @@ +devdesk (0.6.0) bookworm; urgency=medium + + * Light mode, like the other web_template apps (#31): the ☀/◐ button in the + header, shared lt_theme preference (OS setting until you choose), applied + before first paint and synced across tabs. The terminal has a light + palette; Claude runs with its dark-ansi theme so its colours come from + that palette, and the light palette maps bright white to ink, so Claude's + text, scrollback included, stays readable in both modes. tmux and the + statusLine use ANSI colours so they follow the theme too. Sessions pick + up Claude's theme the next time Claude starts in them. + * Command palette (#32): ⌕ button or Ctrl+Shift+P. Switch, create, rename + and end sessions, Claude/Shell window, scroll back, detach, theme, panels, + text size, fullscreen, notifications, copy link, open the repo / PRs / CI + run, show alerts, keyboard help. + * Dialogs instead of browser prompt/confirm for rename, end and new + session (name check inline, project folder picker), and a keyboard + shortcuts sheet in the ⋯ menu, the palette and the footer (#33). + * Toasts for results: created, renamed, ended, uploads, link copied, CI + finished, errors (#34). + * Fix: renames, ends and new sessions often reported "no response" although + they worked; ttyd dropped the reply of commands that exit right after + printing. Replies now wait for the browser to hang up (#35). + * Fix: the Sessions panel showed stale names right after a rename or end. + + -- Jared Vititoe Thu, 08 Oct 2026 00:32:11 -0400 + devdesk (0.5.1) bookworm; urgency=medium * A session created by opening its URL shows up in the Sessions panel and diff --git a/libexec/devdesk-lib.sh b/libexec/devdesk-lib.sh index b32175b..70bf60a 100644 --- a/libexec/devdesk-lib.sh +++ b/libexec/devdesk-lib.sh @@ -43,8 +43,19 @@ DEVDESK_CONTEXT_DIR="$DEVDESK_STATE_DIR/context" DEVDESK_STATUS_DIR="$DEVDESK_STATE_DIR/state" DEVDESK_NAME_RE='^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$' +# Under ttyd, a command that exits right after printing sometimes loses its last +# output: ttyd sees the exit and closes the WebSocket before reading the pty +# (devdesk#35). One-shot replies and errors therefore wait, at most +# DEVDESK_LINGER seconds, for the browser to hang up once it has the answer +# (ttyd then sends SIGHUP). devdesk-serve sets DEVDESK_VIA_TTYD=1. +dd_linger() { + [ "${DEVDESK_VIA_TTYD:-}" = 1 ] && [ -t 0 ] || return 0 + read -r -t "${DEVDESK_LINGER:-1}" _ 2>/dev/null || true +} + dd_die() { printf 'devdesk: %s\n' "$*" >&2 + dd_linger exit 1 } diff --git a/libexec/devdesk-serve b/libexec/devdesk-serve index 3a2fbb1..adf6af5 100755 --- a/libexec/devdesk-serve +++ b/libexec/devdesk-serve @@ -22,4 +22,7 @@ if [ -n "${DEVDESK_BIND:-}" ]; then args+=(--interface "$DEVDESK_BIND") fi +# Tells `devdesk attach` it talks to a browser through ttyd (see dd_linger). +export DEVDESK_VIA_TTYD=1 + exec "${DEVDESK_TTYD:-/usr/lib/devdesk/bin/ttyd}" "${args[@]}" "${DEVDESK_BIN:-/usr/bin/devdesk}" attach diff --git a/libexec/devdesk-statusline b/libexec/devdesk-statusline index 8b46f69..84b0d11 100755 --- a/libexec/devdesk-statusline +++ b/libexec/devdesk-statusline @@ -38,12 +38,13 @@ if [[ $session =~ ^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$ ]]; then fi fi -# Footer line, in TDS colours: session · model · context % · cost +# Footer line: session · model · context % · cost. ANSI colour numbers, not RGB, +# so the browser's dark or light palette applies (devdesk#31). jq -rM --arg s "$session" ' - def c($rgb; $t): "\u001b[38;2;\($rgb)m\($t)\u001b[0m"; + def c($sgr; $t): "\u001b[\($sgr)m\($t)\u001b[0m"; def pct: if . == null then "—" else "\(. | floor)%" end; - [ (if $s != "" then c("255;107;0"; "◈ \($s)") else empty end), - c("0;212;255"; .model.display_name // "Claude"), - c("127;163;191"; "ctx \(.context_window.used_percentage | pct)"), - c("62;96;122"; "$\((.cost.total_cost_usd // 0) * 100 | round / 100)") - ] | join(c("62;96;122"; " · "))' <<<"$input" 2>/dev/null || true + [ (if $s != "" then c("33"; "◈ \($s)") else empty end), + c("36"; .model.display_name // "Claude"), + c("37"; "ctx \(.context_window.used_percentage | pct)"), + c("37"; "$\((.cost.total_cost_usd // 0) * 100 | round / 100)") + ] | join(c("90"; " · "))' <<<"$input" 2>/dev/null || true diff --git a/share/claude-settings.json b/share/claude-settings.json index 95e2d4b..464ad76 100644 --- a/share/claude-settings.json +++ b/share/claude-settings.json @@ -1,5 +1,6 @@ { "preferredNotifChannel": "terminal_bell", + "theme": "dark-ansi", "hooks": { "SessionStart": [ { diff --git a/share/tmux.conf b/share/tmux.conf index 149a206..d861252 100644 --- a/share/tmux.conf +++ b/share/tmux.conf @@ -24,18 +24,21 @@ set -g set-titles-string "devdesk:#S|#T" # --- every new session gets a spare shell next to Claude --------------------- set-hook -g session-created "new-window -d -n shell" -# --- LotusGuild TDS colours --------------------------------------------------- +# --- colours --------------------------------------------------------------------- +# ANSI colour names, not hex: the browser maps them through its dark or light +# palette, so the status bar follows DevDesk's theme (devdesk#31). black is the +# bar, white muted text, cyan the accent, yellow the session name. set -g status-position bottom -set -g status-style "bg=#060c14,fg=#7fa3bf" -set -g status-left "#[fg=#FF6B00,bold] #S #[fg=#3e607a,nobold]│ " +set -g status-style "bg=black,fg=white" +set -g status-left "#[fg=yellow,bold] #S #[fg=brightblack,nobold]│ " set -g status-left-length 40 set -g status-right "" set -g window-status-format " #I:#W " -set -g window-status-current-format "#[fg=#030508,bg=#00D4FF,bold] #I:#W " -set -g window-status-bell-style "fg=#FFB300,bold" -set -g message-style "bg=#0d1520,fg=#00D4FF" -set -g mode-style "bg=#00D4FF,fg=#030508" -set -g pane-border-style "fg=#0d1520" -set -g pane-active-border-style "fg=#00D4FF" +set -g window-status-current-format "#[fg=cyan,reverse,bold] #I:#W " +set -g window-status-bell-style "fg=yellow,bold" +set -g message-style "bg=black,fg=cyan" +set -g mode-style "fg=cyan,reverse" +set -g pane-border-style "fg=brightblack" +set -g pane-active-border-style "fg=cyan" source-file -q /etc/devdesk/tmux.local.conf diff --git a/tests/attach.bats b/tests/attach.bats index 2d3ae6d..4159487 100644 --- a/tests/attach.bats +++ b/tests/attach.bats @@ -327,6 +327,23 @@ posts() { [ -f "$TMP/posts" ] && wc -l <"$TMP/posts" || echo 0; } kill "$RECEIVER" } +@test "under ttyd, one-shot replies wait for the browser to hang up (#35)" { + start_server + # No tty (CLI, tests): no waiting. + start=$(date +%s%N) + DEVDESK_VIA_TTYD=1 DEVDESK_LINGER=2 devdesk attach --list >/dev/null + [ $(( ($(date +%s%N) - start) / 1000000 )) -lt 1500 ] + # On a tty, as under ttyd: the reply is printed, then it waits. + start=$(date +%s%N) + run script -qec "DEVDESK_VIA_TTYD=1 DEVDESK_LINGER=0.6 '$REPO/bin/devdesk' attach --list" /dev/null + elapsed=$(( ($(date +%s%N) - start) / 1000000 )) + [[ $output == *'"sessions"'* ]] + [ "$elapsed" -ge 500 ] + # Errors are printed before the wait, too. + run script -qec "DEVDESK_VIA_TTYD=1 DEVDESK_LINGER=0.3 '$REPO/bin/devdesk' attach --rename nosuch x" /dev/null + [[ $output == *"no session named"* ]] +} + @test "alerts reports unconfigured and unreachable Grafana cleanly" { run env DEVDESK_GRAFANA_URL= devdesk alerts [ "$status" -eq 0 ] diff --git a/web/index.template.html b/web/index.template.html index a3c11ab..e48b3d7 100644 --- a/web/index.template.html +++ b/web/index.template.html @@ -3,11 +3,20 @@ - + DevDesk + @@ -31,6 +40,8 @@ connecting + +
+
devdesk
@@ -128,6 +140,9 @@ ⇧dragselect │ drop / pasteattach files + │ + ^⇧Pcommands +