From 051f8364f28e9397edeab0c370cd77544903135d Mon Sep 17 00:00:00 2001 From: jean-baptiste Date: Tue, 11 Aug 2026 15:54:08 +0200 Subject: [PATCH 1/5] docs: refresh README + project docs post-migration Credit doctly/switchboard upstream and highlight what the fork adds (new About-this-fork section). Fix stale devsuitup migration leftovers (download links, repo refs in .ai/). Correct the cp-is-safe claim that contradicted the 2026-06-04 appimagelauncherd incident. Add the missing user docs for schedules + triggers (docs/automation.md), document the chain field in the trigger-watcher context, extend the fork-features list past PR #20, refresh the release gotchas (ruleset approval, draft publish step) and the project-structure tree. --- .ai/contexts/trigger-watcher.md | 3 +- .ai/shared-guidelines.md | 14 ++++-- README.md | 48 +++++++++++++++----- docs/README.md | 6 ++- docs/automation.md | 79 +++++++++++++++++++++++++++++++++ docs/customizing-colors.md | 2 +- 6 files changed, 135 insertions(+), 17 deletions(-) create mode 100644 docs/automation.md diff --git a/.ai/contexts/trigger-watcher.md b/.ai/contexts/trigger-watcher.md index 358e3d31..6dab0a8a 100644 --- a/.ai/contexts/trigger-watcher.md +++ b/.ai/contexts/trigger-watcher.md @@ -43,7 +43,8 @@ Drop a file at `SWITCHBOARD_TRIGGERS_DIR/.json` (default `~/.switchboard/t Fields: - `sessionId` — must match a key in `activeSessions` (`main.js`) -- `command` — written verbatim as `command + '\r'` to the PTY +- `command` — written to the PTY, then Enter is sent as a SEPARATE write (discrete submit; a `\r` concatenated onto the text can be absorbed by the composer). Mutually exclusive with `chain`. +- `chain` — array of up to 20 `{command, ...}` steps (`MAX_CHAIN_LENGTH`), injected sequentially; each step's submission is verified (busy-rise) with one bare-Enter retry before the next step is sent. Mutually exclusive with `command`; exactly one of the two is required. - `wait` — `"none"` (default) | `"idle"`. `"idle"` polls `isSessionBusy` every 100 ms until the session goes idle or the timeout fires. - `timeout_ms` — optional positive integer, ≤ 600 000 ms. Overrides both the env var and the default for this trigger only. On invalid value → `{ok:false, error:"invalid timeout_ms"}`, semaphore released, no PTY write. diff --git a/.ai/shared-guidelines.md b/.ai/shared-guidelines.md index f59a4e4f..d7680c49 100644 --- a/.ai/shared-guidelines.md +++ b/.ai/shared-guidelines.md @@ -2,7 +2,7 @@ @~/.skaleet-ai/conventions/rules.md -This is JB's fork (`JeanBaptisteRenard/switchboard`) of `doctly/switchboard`. The fork carries features not (yet) upstream — read this before editing anything. +This is JB's fork (`devsuitup/switchboard`, transferred from `JeanBaptisteRenard/switchboard` on 2026-08-11) of `doctly/switchboard`. The fork carries features not (yet) upstream — read this before editing anything. **Caveat on the universal rules import above**: Switchboard is an **Electron desktop app**, not a Skaleet backend service. The following sections from `rules.md` do NOT apply here: - DDD/CQRS architecture (no Domain/Application/Infrastructure layers — this is a renderer + main-process app) @@ -81,7 +81,7 @@ If you're working autonomously (overnight, AFK mode) while the user's AppImage i ## Fork-specific features (not in upstream) -These exist on `JeanBaptisteRenard/switchboard` main but not on `doctly/switchboard` main. If an agent claims a feature is "upstream", verify with `git log upstream/main -- `: +These exist on `devsuitup/switchboard` main but not on `doctly/switchboard` main. If an agent claims a feature is "upstream", verify with `git log upstream/main -- `: - **Subagent support** — index, search, transcript viewer (PR #47 upstream, merged on fork) - **Subagent observability** — hierarchy, live transitions, status badges (PR #48 upstream) @@ -94,6 +94,14 @@ These exist on `JeanBaptisteRenard/switchboard` main but not on `doctly/switchbo - **`SWITCHBOARD_DATA_DIR`** env var for DB isolation in dev (fork) - **Wayland clipboard fix** — main-process IPC + OSC 52 (fork PR #18 = port of upstream PR #55) - **Missing project remap** — detect + UI + atomic JSONL rewrite (fork PR #20 = port of upstream PR #35, with subagent-aware enum + active-session guard added on top) +- **Trigger watcher** — file-based command injection into open PTYs, single + chained (fork PR #24 and follow-ups); see [contexts/trigger-watcher.md](contexts/trigger-watcher.md) +- **Schedule runner** — in-process cron spawning headless Claude tasks from `schedule-*.md` files; see [contexts/schedule-runner.md](contexts/schedule-runner.md) +- **Session restore** — persist + restore the open working set across restarts (fork PR #80) +- **Perf campaign v0.0.33–41** — 30fps terminal flush cap, WebGL virtualization, LRU xterm cap, targeted refreshes, idle-CPU fixes (fork PRs #55–#70) +- **Search off the main thread + bounded FTS query** — worker relay + 48-char cap (fork PR #97, v0.0.44) +- **Resume/fork in real recorded cwd** for worktree sessions (fork PR #96, v0.0.44) + +(Not exhaustive — `git log --oneline upstream/main..main` is the ground truth.) ## Patterns to reuse, not reinvent @@ -118,7 +126,7 @@ These exist on `JeanBaptisteRenard/switchboard` main but not on `doctly/switchbo 1. `task check` (lint + test). 0 errors. Pre-existing warnings are fine. 2. Squash to clear commits. No `Co-Authored-By`. Imperative subject, brief why-body. -3. `gh pr create` against `JeanBaptisteRenard/switchboard:main` (the fork's main, not upstream's). Title format: `(area): short imperative`. +3. `gh pr create` against `devsuitup/switchboard:main` (the fork's main, not upstream's). Title format: `(area): short imperative`. 4. If the change is a port of an upstream PR, **credit the upstream author** in the body with a link. We want abasiri to see we're not stealing. ## Upstreaming work diff --git a/README.md b/README.md index 914437ea..35b5c3f0 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,22 @@ Switchboard is a desktop app that gives you a unified view of all your Claude Co ![Switchboard](build/screenshot.png) +## About this fork + +This repository is a fork of **[doctly/switchboard](https://github.com/doctly/switchboard)** — all credit for the original app goes to its author. The fork tracks upstream (features are ported in both directions when they fit) and is currently ~120 commits ahead. Highlights of what it adds: + +- **Subagent support & observability** — subagent transcripts are indexed and searchable, the sidebar shows the parent→child hierarchy with live spawn/completion indicators and status badges, and clicking a subagent opens a read-only transcript viewer instead of re-spawning Claude. See [docs/subagents.md](docs/subagents.md). +- **Session restore** — reopen your whole working set (open sessions, order, active one) after a restart. See [docs/session-restore.md](docs/session-restore.md). +- **Automation** — an in-process scheduler fires Claude tasks from cron-style `schedule-*.md` files, and a file-based trigger API lets external scripts inject commands (single or chained) into open sessions. See [docs/automation.md](docs/automation.md). +- **Performance work** — terminal write-flush capped at ~30 fps, WebGL context virtualization and LRU caps on grid cards, differentiated scrollback, targeted watcher refreshes, header-only reads for cached sessions, idle-CPU fixes. Measured compositor load on a busy grid dropped from 40–60% to 8–10%. +- **Search hardening** — full-text search runs in a dedicated worker thread with bounded query length, so a pasted URL can no longer freeze the app; explicit reindex via Enter or the refresh button. +- **Worktree tools** — sessions started in a git worktree resume and fork in their real recorded cwd; a rich delete dialog shows dirty-file status before removing a worktree from disk. +- **Work Files tab** — browse, format (JSON/JSONL), and delete each project's `.work-files/` scratch space from the sidebar. +- **Robustness** — single-instance lock (no PTY loss when double-launching), `SWITCHBOARD_DATA_DIR` env var to isolate a dev database from the daily driver, missing-project detection + remap UI, Wayland clipboard support (OSC 52). +- **CI** — test coverage with a patch-coverage gate (80% on changed lines). + +`git log --oneline upstream/main..main` lists everything the fork carries. + ### Key Features - **Session Browser** — All your Claude Code sessions, organized by project, searchable by content @@ -74,12 +90,14 @@ Full user-facing documentation lives in [docs/](docs/README.md): - [Plans, Memory, and Work Files](docs/plans-memory-workfiles.md) — CodeMirror panels for plan files, CLAUDE.md, and `.work-files/` - [Activity Stats](docs/activity-stats.md) — coding activity heatmap - [Settings Reference](docs/settings.md) — every field in Global and Project Settings +- [Automation](docs/automation.md) — scheduled Claude tasks (cron) and the file-based trigger API +- [Customizing Colors](docs/customizing-colors.md) — community guide (in French) to theming via `app.asar` ## Download Grab the latest release for your platform: -**[Download Switchboard](https://github.com/doctly/switchboard/releases/latest)** +**[Download Switchboard](https://github.com/devsuitup/switchboard/releases/latest)** - **macOS**: `.dmg` (Apple Silicon & Intel) - **Windows**: `.exe` installer @@ -101,7 +119,7 @@ Grab the latest release for your platform: ```bash task install # npm install task dev # launch Electron (--no-sandbox, required on Linux) -task test # node --test (24 tests) +task test # node --test task lint # eslint . task check # test + lint — pre-commit / pre-push gate task ci # same as check but sequential, verbose @@ -140,7 +158,7 @@ If your `~/Applications/Switchboard.AppImage` is open while you develop: - **Dev DB isolation** — `task dev` sets `SWITCHBOARD_DATA_DIR=~/.switchboard-dev` automatically so the dev electron uses its own SQLite database. The AppImage keeps using `~/.switchboard/switchboard.db`. They never collide. - **Single-instance lock** — if you double-click `Switchboard.AppImage` while it's already open, the second launch quits immediately and focuses the existing window instead of spawning a duplicate process. This was a real data-loss bug (PTYs orphaned) before the fix landed. -- **Rebuilding is risky, replacing is safe** — `task build` invokes `electron-builder`, which rebuilds native modules (`better-sqlite3`, `node-pty`) by default. Those `.node` files are loaded by your running AppImage; replacing them mid-run can kill the process (witnessed 2026-05-31). **Quit Switchboard before running `task build`** unless you've confirmed `--config.npmRebuild=false` is in effect. **However**, replacing `~/Applications/Switchboard.AppImage` via `cp` AFTER the build is safe — the live process is fully extracted to `/tmp/.mount_*/` and doesn't need the on-disk file. The new code takes effect only on next launch. +- **Rebuilding AND replacing are both risky while the app runs** — `task build` invokes `electron-builder`, which rebuilds native modules (`better-sqlite3`, `node-pty`) by default. Those `.node` files are loaded by your running AppImage; replacing them mid-run can kill the process (witnessed 2026-05-31). Building is safe only with `--config.npmRebuild=false`. Replacing `~/Applications/Switchboard.AppImage` via `cp` is **not reliably safe either**: the live process doesn't need the on-disk file (it runs from `/tmp/.mount_*/`), but `appimagelauncherd` watches `~/Applications/` and its desktop-integration re-run can cleanly terminate the running instance (witnessed 2026-06-04, non-deterministic). Do the `cp` only when you're ready to restart. The new code takes effect only on next launch. ### For AI agents @@ -202,7 +220,8 @@ Set `GH_TOKEN` in your environment (a GitHub personal access token with `repo` s This fork's `main` is branch-protected and its `main` branch tracks `upstream/main`, both of which trip up the generic release flow above: - **A bare `git push` (no remote) targets `upstream` (`doctly/switchboard`), not `origin`** — it will fail with a permissions error. Always `git push origin ...` explicitly. -- **Direct pushes to `main` are rejected** ("repository rule violations"). The version-bump commit must land via a PR (admin-merge is fine). +- **Direct pushes to `main` are rejected** ("repository rule violations"). The version-bump commit must land via a PR. The ruleset requires 1 approving review from a different account plus green `test (20)` / `test (22)` checks; arm `gh pr merge --auto` and approve from the other account. +- **The release workflow leaves the release as a draft on purpose** — after all assets are uploaded (19 expected), publish manually: `gh release edit v --draft=false --latest --notes "..."`. - **Tag the merged commit on `main`, after the PR merges — never the local pre-merge bump commit.** Tagging first and then squash-merging creates a tag that isn't an ancestor of `main`; `git describe --tags` and release-notes generation then skip it. Correct order: PR-merge the bump → `git reset --hard origin/main` locally → tag → `git push origin `. If a build already started from a bad tag, cancel the run (`gh run cancel`) and delete + recreate the tag. - **Unsigned macOS builds need both `"mac": {"identity": null}` in `package.json` and `notarize: false`.** A `CSC_LINK` env that's set-but-empty (e.g. `${{ secrets.CSC_LINK || '' }}`) is read by electron-builder as a certificate *file path* — `stat('')` resolves to the CI working directory, and the mac job fails with `... not a file` only on tag-triggered (release) runs, never on PR builds. Gating `CSC_IDENTITY_AUTO_DISCOVERY` on whether the secret is set does **not** fix this; `identity: null` does. - **`gh release upload`/`create` can hit an intermittent 401 from `uploads.github.com` on a single asset** (often a `.blockmap`), aborting a batch upload and leaving a partial draft release. Upload assets one at a time with a retry loop (`for i in 1..5; do gh release upload "$TAG" "$f" --clobber && break; sleep 2; done`) instead of a single `gh release create ... dist/*` call. @@ -228,11 +247,18 @@ The macOS build uses custom entitlements (`build/entitlements.mac.plist`) to all ## Project Structure ``` -main.js Electron main process -preload.js Context bridge (IPC bindings) -db.js SQLite session cache & metadata -public/ Renderer (HTML/CSS/JS) -scripts/ Build & postinstall scripts -build/ Icons, entitlements, builder resources -.github/workflows/ CI/CD +main.js Electron main process (IPC, PTY sessions, watchers) +preload.js Context bridge (IPC bindings) +db.js SQLite session cache, metadata, FTS search +session-cache.js JSONL indexer + projects watcher +schedule-runner.js In-process cron for scheduled Claude tasks +trigger-watcher.js File-based command-injection API +workers/ Worker threads (indexing, search queries) +public/ Renderer (HTML/CSS/JS) +test/ node:test suites (jsdom for renderer files) +docs/ User-facing documentation +.ai/ Agent guidelines + architecture context docs +scripts/ Build & postinstall scripts +build/ Icons, entitlements, builder resources +.github/workflows/ CI/CD ``` diff --git a/docs/README.md b/docs/README.md index bdde24da..ebe8b89c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,6 +2,8 @@ Switchboard is a desktop command center for Claude Code sessions. It gives you a unified window across all your projects — launch, resume, monitor, and search sessions without leaving the app. +This is the documentation for the [devsuitup/switchboard](https://github.com/devsuitup/switchboard) fork of [doctly/switchboard](https://github.com/doctly/switchboard). See the [README's "About this fork"](../README.md#about-this-fork) section for what the fork adds on top of upstream. + ## Pages - [Session Browser](session-browser.md) — sidebar, project grouping, full-text search, archive, star, filters @@ -15,10 +17,12 @@ Switchboard is a desktop command center for Claude Code sessions. It gives you a - [Plans, Memory, and Work Files](plans-memory-workfiles.md) — edit plan files, CLAUDE.md, and `.work-files/` in CodeMirror panels - [Activity Stats](activity-stats.md) — coding activity heatmap - [Settings Reference](settings.md) — every field in Global and Project Settings +- [Automation](automation.md) — scheduled Claude tasks (cron) and the file-based trigger API +- [Customizing Colors](customizing-colors.md) — community guide (in French) to theming via `app.asar` ## Download / Install -Grab the latest release for your platform from the [GitHub Releases page](https://github.com/doctly/switchboard/releases/latest): +Grab the latest release for your platform from the [GitHub Releases page](https://github.com/devsuitup/switchboard/releases/latest): - **macOS**: `.dmg` (Apple Silicon and Intel) - **Windows**: `.exe` installer diff --git a/docs/automation.md b/docs/automation.md new file mode 100644 index 00000000..3d89f70b --- /dev/null +++ b/docs/automation.md @@ -0,0 +1,79 @@ +# Automation + +Switchboard can run Claude tasks without you at the keyboard, through two complementary mechanisms: + +- **Schedules** — cron-style recurring tasks defined as Markdown files, fired by an in-process scheduler. +- **Triggers** — one-shot command injection into an already-open session, driven by dropping a JSON file. Meant for external scripts and harnesses. + +## Schedules + +A schedule is a Markdown file at `/.claude/commands/schedule-*.md` with YAML frontmatter: + +```markdown +--- +name: My morning audit +cron: 0 9 * * 1-5 +enabled: true +slug: morning-audit +cli: + permission-mode: acceptEdits + allowed-tools: Bash,Read,Write +--- + + +``` + +When the cron expression matches, Switchboard pre-seeds a new session with the prompt and spawns `claude --resume -p "..."` headlessly. The run appears as a regular session in the sidebar — open it there to see the result. + +### Creating a schedule + +Click the **clock icon** on a project in the sidebar. This opens an interactive Claude session pre-loaded with a schedule-creator command: describe what you want scheduled, and Claude writes the `schedule-*.md` file for you. You can also write the file by hand — the scheduler rescans every minute, so changes take effect within 60 seconds, no restart needed. + +Existing schedules are listed in the project's brain tab (Plans/Memory panel), each with a **run now** button that fires it immediately, bypassing the cron match. + +### Behavior and limits + +- `enabled: false` disables a schedule without deleting it. +- `cron` is standard 5-field syntax (minute, hour, day-of-month, month, day-of-week) with `*`, lists, ranges, and steps. No `@daily` aliases, no DST awareness (times are local). +- `permission-mode: acceptEdits` (or `auto`) is the practical default — headless `-p` runs hang on any permission prompt otherwise. +- One run at a time per schedule: if a run is still going when the next tick matches, that tick is silently skipped. +- The scheduler lives in-process: **if Switchboard isn't running, the schedule doesn't fire.** It's a personal tool, not a daemon. + +## Triggers + +The trigger watcher lets any external script type into an open session's terminal — no Electron IPC required. Drop a JSON file into `~/.switchboard/triggers/` (override with `SWITCHBOARD_TRIGGERS_DIR`): + +```json +{ + "sessionId": "abc-123-def", + "command": "/compact", + "wait": "idle", + "timeout_ms": 120000 +} +``` + +- `sessionId` — the target session (must be open in Switchboard). +- `command` — written to the PTY, followed by a discrete Enter keypress. +- `wait` — `"none"` (default) sends immediately; `"idle"` waits until the session stops being busy before sending. Use `"idle"` for anything that must not interrupt a mid-response stream. +- `timeout_ms` — optional cap on the idle wait (≤ 600 000 ms; default 300 000). + +Instead of a single `command`, you can send a `chain` — a sequence of up to 20 steps injected one after another, each submitted and verified before the next: + +```json +{ + "sessionId": "abc-123-def", + "chain": [{ "command": "/compact" }], + "wait": "none" +} +``` + +`command` and `chain` are mutually exclusive. + +The trigger file is deleted after processing, and a result file is written to `~/.switchboard/triggers/processed/.result.json`: + +```json +{ "ok": true, "sessionId": "...", "command": "...", "sent_at": "...", "waited_ms": 320 } +{ "ok": false, "error": "", "sessionId": "..." } +``` + +The primary use case is context-management harnesses — e.g. an agent hook that detects a full context window and injects `/compact` into its own session. Write the trigger file atomically (write to a temp name, then rename) so the watcher never reads a half-written file. diff --git a/docs/customizing-colors.md b/docs/customizing-colors.md index dd3ff425..34de61e9 100644 --- a/docs/customizing-colors.md +++ b/docs/customizing-colors.md @@ -30,7 +30,7 @@ L'interface est livrée avec un **thème sombre** bleu-violet. Comme l'applicati ## 3. Installer Switchboard -1. Ouvrez la page des versions : `https://github.com/doctly/switchboard/releases/latest` +1. Ouvrez la page des versions : `https://github.com/devsuitup/switchboard/releases/latest` 2. Dans **Assets**, téléchargez l'installeur Windows `.exe` (type `Switchboard-Setup-x.y.z.exe`). *(macOS : `.dmg` ; Linux : `.AppImage` ou `.deb`.)* 3. Lancez l'`.exe`. Comme l'application n'est pas signée, **SmartScreen** affichera « Windows a protégé votre PC » → cliquez sur **Informations complémentaires** puis **Exécuter quand même**. 4. Suivez l'installeur. Au premier lancement, Switchboard scanne `~/.claude/projects` et liste vos sessions par projet. From 9af088d9ec2fe684ad0b9ad9f5c1305f491ab925 Mon Sep 17 00:00:00 2001 From: jean-baptiste Date: Fri, 14 Aug 2026 09:19:43 +0200 Subject: [PATCH 2/5] =?UTF-8?q?docs:=20fix=20review=20findings=20(PR=20ran?= =?UTF-8?q?ges,=20stale=20counts,=20=C2=A72=20heading,=20fork=20note)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .ai/contexts/trigger-watcher.md | 2 +- .ai/shared-guidelines.md | 4 ++-- README.md | 2 +- docs/customizing-colors.md | 2 ++ 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.ai/contexts/trigger-watcher.md b/.ai/contexts/trigger-watcher.md index 6dab0a8a..1a19058c 100644 --- a/.ai/contexts/trigger-watcher.md +++ b/.ai/contexts/trigger-watcher.md @@ -6,7 +6,7 @@ | File | LOC | Role | |---|---|---| -| `trigger-watcher.js` | ~175 | The entire module: directory setup, `fs.watch` listener, idle-wait logic, PTY write, result file. | +| `trigger-watcher.js` | ~800 | The entire module: directory setup, `fs.watch` listener, idle-wait logic, single + chained trigger processing, submit-with-verify busy-rise/fall polling, input validation, PTY write, result file. | | `main.js` (wiring) | 15 | `require('./trigger-watcher').start(ctx)` in the `app.whenReady` block, right after `startScheduler`. | ## Public surface diff --git a/.ai/shared-guidelines.md b/.ai/shared-guidelines.md index d7680c49..a306ecb8 100644 --- a/.ai/shared-guidelines.md +++ b/.ai/shared-guidelines.md @@ -44,7 +44,7 @@ task dev # Taskfile already sets SWITCHBOARD_DATA_DIR=~/.switchboard-dev by de The AppImage uses `~/.switchboard/switchboard.db`. The dev electron uses `~/.switchboard-dev/switchboard.db`. They cannot collide. -### 2. Running `npm run build:linux` CAN kill the running instance — `cp` does not +### 2. Running `npm run build:linux` CAN kill the running instance — and so can the `cp` to ~/Applications **Corrected 2026-05-31** — the previous version of this section claimed the build was safe. It isn't. @@ -97,7 +97,7 @@ These exist on `devsuitup/switchboard` main but not on `doctly/switchboard` main - **Trigger watcher** — file-based command injection into open PTYs, single + chained (fork PR #24 and follow-ups); see [contexts/trigger-watcher.md](contexts/trigger-watcher.md) - **Schedule runner** — in-process cron spawning headless Claude tasks from `schedule-*.md` files; see [contexts/schedule-runner.md](contexts/schedule-runner.md) - **Session restore** — persist + restore the open working set across restarts (fork PR #80) -- **Perf campaign v0.0.33–41** — 30fps terminal flush cap, WebGL virtualization, LRU xterm cap, targeted refreshes, idle-CPU fixes (fork PRs #55–#70) +- **Perf campaign v0.0.33–41** — 30fps terminal flush cap, WebGL virtualization, LRU xterm cap, targeted refreshes, idle-CPU fixes (fork PRs #61–#83) - **Search off the main thread + bounded FTS query** — worker relay + 48-char cap (fork PR #97, v0.0.44) - **Resume/fork in real recorded cwd** for worktree sessions (fork PR #96, v0.0.44) diff --git a/README.md b/README.md index 35b5c3f0..bd64737b 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Switchboard is a desktop app that gives you a unified view of all your Claude Co ## About this fork -This repository is a fork of **[doctly/switchboard](https://github.com/doctly/switchboard)** — all credit for the original app goes to its author. The fork tracks upstream (features are ported in both directions when they fit) and is currently ~120 commits ahead. Highlights of what it adds: +This repository is a fork of **[doctly/switchboard](https://github.com/doctly/switchboard)** — all credit for the original app goes to its author. The fork tracks upstream (features are ported in both directions when they fit) and is currently ~160 commits ahead. Highlights of what it adds: - **Subagent support & observability** — subagent transcripts are indexed and searchable, the sidebar shows the parent→child hierarchy with live spawn/completion indicators and status badges, and clicking a subagent opens a read-only transcript viewer instead of re-spawning Claude. See [docs/subagents.md](docs/subagents.md). - **Session restore** — reopen your whole working set (open sessions, order, active one) after a restart. See [docs/session-restore.md](docs/session-restore.md). diff --git a/docs/customizing-colors.md b/docs/customizing-colors.md index 34de61e9..a79eee67 100644 --- a/docs/customizing-colors.md +++ b/docs/customizing-colors.md @@ -3,6 +3,8 @@ > Guide pratique pour installer Switchboard, changer la couleur de fond, et appliquer un thème clair façon Claude / Cowork (fond crème, texte foncé, accent corail). > > Auteur : Jean-Luc PIETRI — 23/05/2026 — testé sur Switchboard v0.0.30 (Windows). +> +> *Note (2026-08) : guide écrit pour `doctly/switchboard` v0.0.30 ; ce dépôt est le fork `devsuitup/switchboard`, le lien d'installation ci-dessous pointe donc vers les releases du fork. Le contenu s'applique tel quel.* --- From df79bc7f84c0f8ffaed3af15ecb1b77614649407 Mon Sep 17 00:00:00 2001 From: jean-baptiste Date: Fri, 14 Aug 2026 09:22:12 +0200 Subject: [PATCH 3/5] =?UTF-8?q?docs:=20fix=20perf-campaign=20PR=20range=20?= =?UTF-8?q?=E2=80=94=20feature=20PRs,=20not=20release-bump=20PRs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .ai/shared-guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ai/shared-guidelines.md b/.ai/shared-guidelines.md index a306ecb8..7cca241f 100644 --- a/.ai/shared-guidelines.md +++ b/.ai/shared-guidelines.md @@ -97,7 +97,7 @@ These exist on `devsuitup/switchboard` main but not on `doctly/switchboard` main - **Trigger watcher** — file-based command injection into open PTYs, single + chained (fork PR #24 and follow-ups); see [contexts/trigger-watcher.md](contexts/trigger-watcher.md) - **Schedule runner** — in-process cron spawning headless Claude tasks from `schedule-*.md` files; see [contexts/schedule-runner.md](contexts/schedule-runner.md) - **Session restore** — persist + restore the open working set across restarts (fork PR #80) -- **Perf campaign v0.0.33–41** — 30fps terminal flush cap, WebGL virtualization, LRU xterm cap, targeted refreshes, idle-CPU fixes (fork PRs #61–#83) +- **Perf campaign v0.0.33–41** — 30fps terminal flush cap, WebGL virtualization, LRU xterm cap, targeted refreshes, idle-CPU fixes (fork PRs #55–#70; a second perf wave #73–#76 shipped in v0.0.38) - **Search off the main thread + bounded FTS query** — worker relay + 48-char cap (fork PR #97, v0.0.44) - **Resume/fork in real recorded cwd** for worktree sessions (fork PR #96, v0.0.44) From 000a3228d046451adabbf672274bc1806e43c79b Mon Sep 17 00:00:00 2001 From: jean-baptiste Date: Fri, 14 Aug 2026 09:44:50 +0200 Subject: [PATCH 4/5] docs: remove Skaleet-internal references (review feedback) --- .ai/contexts/session-cache.md | 2 +- .ai/contexts/trigger-watcher.md | 1 - .ai/shared-guidelines.md | 16 +++------------- .gitignore | 2 +- AGENTS.md | 26 -------------------------- test/db-search-query-bound.test.js | 4 ++-- 6 files changed, 7 insertions(+), 44 deletions(-) diff --git a/.ai/contexts/session-cache.md b/.ai/contexts/session-cache.md index dfe40523..03d9a2ff 100644 --- a/.ai/contexts/session-cache.md +++ b/.ai/contexts/session-cache.md @@ -45,7 +45,7 @@ From `derive-project-path.js`: `deriveProjectPath(folderPath)`, `resolveWorktree ## Non-obvious behaviors -- **`resolveWorktreePath` collapses `/.worktrees/` → ``** when the parent dir exists. Consequence: many `~/.claude/projects/-home-...workspace-skaleet-ai--worktrees-X` folders derive to the same projectPath. Callers must dedupe (see `get-work-files` IPC for the pattern). +- **`resolveWorktreePath` collapses `/.worktrees/` → ``** when the parent dir exists. Consequence: many `~/.claude/projects/-home-...workspace-myproject--worktrees-X` folders derive to the same projectPath. Callers must dedupe (see `get-work-files` IPC for the pattern). - **Two-table sidebar payload**: projects are aggregated, but each session row has its own `subagentType` field. A `null`/empty `subagentType` means it's a parent session; anything else (e.g. `'general-purpose'`, `'researcher'`) marks a subagent. - **`fs.watch` debouncing**: the watcher batches per-folder events in a `pendingChanges = Map | true>` for ~200 ms before flushing to `refreshFolder`. A `true` value means "full walk needed" (rare path). - **Stats `firstSessionDate`** is computed from `MIN(modified)`, not `MIN(created)`. Old sessions touched by recent reads keep their original `created` but their `modified` reflects the latest indexing — by design (the heatmap measures activity, not creation). diff --git a/.ai/contexts/trigger-watcher.md b/.ai/contexts/trigger-watcher.md index 1a19058c..58629505 100644 --- a/.ai/contexts/trigger-watcher.md +++ b/.ai/contexts/trigger-watcher.md @@ -79,4 +79,3 @@ Trigger file is **deleted** after processing (success or failure). - If you rename `_cliBusy` on `session` in `main.js`, update `isSessionBusy` in the `start(ctx)` wiring block. - If you rename `activeSessions` or change the structure (`session.pty` → `session.ptyProcess`), update both `getPtyForSession` and `isSessionBusy` in the wiring block. - Tests live in `test/trigger-watcher.test.js`. They use `SWITCHBOARD_TRIGGERS_DIR` env override — do not hardcode paths there. -- The convention doc for harness script authors lives at `~/.skaleet-ai/conventions/how-to/switchboard-trigger.md`. diff --git a/.ai/shared-guidelines.md b/.ai/shared-guidelines.md index 7cca241f..3ee29209 100644 --- a/.ai/shared-guidelines.md +++ b/.ai/shared-guidelines.md @@ -1,18 +1,8 @@ # Switchboard — Notes for Claude (and other AI agents) -@~/.skaleet-ai/conventions/rules.md - This is JB's fork (`devsuitup/switchboard`, transferred from `JeanBaptisteRenard/switchboard` on 2026-08-11) of `doctly/switchboard`. The fork carries features not (yet) upstream — read this before editing anything. -**Caveat on the universal rules import above**: Switchboard is an **Electron desktop app**, not a Skaleet backend service. The following sections from `rules.md` do NOT apply here: -- DDD/CQRS architecture (no Domain/Application/Infrastructure layers — this is a renderer + main-process app) -- `docker compose exec` runtime gating (we run npm / node directly on the host; only deps for the *target* repos are Dockerised) -- `monitor-ci` skill (we use GitHub Actions, not GitLab CI; check status via `gh pr checks`) -- `glab` rules (replaced by `gh` CLI for this fork) -- `/pre-commit` skill (husky pre-commit runs `task check` automatically; the skill is for Skaleet PHP projects) -- Conventional Commits — we use a looser style (`feat(scope): ...`, `fix(scope): ...`, but no strict footer rules) - -Everything else (HANDOFF protocol, agent dispatch rules, sub-agent model gate, worktree isolation requirement, no Co-Authored-By, shell-command pitfalls, memory hygiene) **does apply**. +Switchboard is an **Electron desktop app**: renderer + main-process, no Domain/Application/Infrastructure layering. Runtimes (npm, node) run directly on the host — nothing here is Dockerised. CI is GitHub Actions (check status via `gh pr checks`); the `gh` CLI is used for PRs, not `glab`. The husky pre-commit hook runs `task check` automatically. Commit style is a loose `feat(scope): ...` / `fix(scope): ...`, with no strict footer rules. ## Quick orientation @@ -67,7 +57,7 @@ After the agent completes, **remove the worktree manually** — `git worktree re ### 4. `.work-files/` is gitignored scratch space -Skaleet workspace convention. Use it for session notes, proposals, plans, scratch JSONLs. It's enumerated by the Work Files sidebar tab — files appear there automatically. +Gitignored scratch space. Use it for session notes, proposals, plans, scratch JSONLs. It's enumerated by the Work Files sidebar tab — files appear there automatically. ### 5. No `Co-Authored-By` trailers in commits @@ -77,7 +67,7 @@ Workspace-level rule (`~/workspace/CLAUDE.md`). Applies to commits and MR/PR des If you're working autonomously (overnight, AFK mode) while the user's AppImage is live with an active session open, treat the app as **read-only from the outside** for the duration: no `npm run build:linux` / `task build` without the `--config.npmRebuild=false` flag (§2), no `cp` to `~/Applications/Switchboard.AppImage` (§2 — `appimagelauncherd` can silently kill the running instance), and no second `npx electron .` (§1 — it just quits and steals focus instead of giving you a usable dev process). None of these produce an obvious error at the time you run them; the damage shows up later as a dead session the user didn't ask to lose. If you need a live process to test against, use `SWITCHBOARD_DATA_DIR` isolation (§1) and only do the disruptive steps (uncontrolled rebuild, `cp` swap) once the user is ready to restart. -> This is a Switchboard-specific writeup of a more general pattern — "don't touch shared mutable state a human is actively using" applies to any AI agent working unattended alongside a live app. Worth considering as a skaleet-ai convention someday; not proposed here. +> This is a Switchboard-specific writeup of a more general pattern — "don't touch shared mutable state a human is actively using" applies to any AI agent working unattended alongside a live app. ## Fork-specific features (not in upstream) diff --git a/.gitignore b/.gitignore index bb581b38..51514e03 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,7 @@ public/codemirror-bundle.js .cache/ dev-app-update.yml -# Scratch space (Skaleet workspace convention) +# Gitignored scratch space, surfaced in the Work Files sidebar tab .work-files/ diff --git a/AGENTS.md b/AGENTS.md index 8c2b29b6..1dc91cf7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,31 +1,5 @@ # AGENTS.md -## Universal Rules - -Read and follow `~/.skaleet-ai/conventions/rules.md` — it contains mandatory rules for all Skaleet projects (architecture, testing, git, AI agent behavior, shell commands). - ## Local Instructions Read `.ai/shared-guidelines.md` at the start of any work and follow it as the single source of truth for this project. - -## Conventions - -Shared development conventions are available at `~/.skaleet-ai/conventions/`. Read the relevant guide when working on a specific topic: - -- **Architecture**: `conventions/architecture.md` — DDD/CQRS, Clean Architecture, bounded context isolation -- **Code Style**: `conventions/code-style.md` — PSR-12, naming conventions -- **TDD**: `conventions/tdd/_base.md` + language-specific (`php.md`, `typescript.md`) -- **Commit Messages**: `conventions/how-to/commit-messages.md` — Conventional Commits (mandatory) -- **How-To Guides**: `conventions/how-to/` — command-handler, api-endpoint, datagrid, etc. - -## CLI Tools - -Shared CLI tools are installed in `~/.skaleet-ai/bin/`. Use the full path to invoke them: - -- **lsp-query**: Code intelligence via LSP (definition, references, hover, rename, diagnostics). Use when the IDE is not available. Usage: `~/.skaleet-ai/bin/lsp-query [line] [col] [extra]`. Read `~/.skaleet-ai/conventions/how-to/lsp-query.md` for full documentation. - -## Project Detection - -- `composer.json` with PHP require -> PHP project (read `conventions/tdd/php.md`) -- `tsconfig.json` -> TypeScript project (read `conventions/tdd/typescript.md`) -- Check project's `.ai/shared-guidelines.md` for project-specific rules diff --git a/test/db-search-query-bound.test.js b/test/db-search-query-bound.test.js index 218023c3..a55cf97b 100644 --- a/test/db-search-query-bound.test.js +++ b/test/db-search-query-bound.test.js @@ -98,7 +98,7 @@ test('replica: query containing a double-quote is escaped', () => { }); test('replica: 60-char URL is truncated to ≤ FTS_QUERY_MAX_CHARS before quoting', () => { - const url = 'https://gitlab.com/skaleet/product/tagpay/-/merge_requests/25629'; + const url = 'https://gitlab.example.com/product/example-project/-/merge_requests/25629'; assert.ok(url.length > FTS_QUERY_MAX_CHARS, 'test URL must be longer than the cap'); const expr = buildMatchExpression(url); // The phrase content (without surrounding quotes) must be ≤ cap @@ -110,7 +110,7 @@ test('replica: 60-char URL is truncated to ≤ FTS_QUERY_MAX_CHARS before quotin }); test('replica: trigram count for bounded URL is ≤ 46 (phrase-intersect safe for main thread)', () => { - const url = 'https://gitlab.com/skaleet/product/tagpay/-/merge_requests/25629'; + const url = 'https://gitlab.example.com/product/example-project/-/merge_requests/25629'; const expr = buildMatchExpression(url); const inner = expr.replace(/^"|"$/g, ''); const ngrams = trigramCount(inner); From 724938c9b31e2ebaa629b91023441fbe87c0289a Mon Sep 17 00:00:00 2001 From: jean-baptiste Date: Fri, 14 Aug 2026 09:52:19 +0200 Subject: [PATCH 5/5] docs(ai): distill portable agent practices into the repo (post-import removal) --- .ai/agent-practices.md | 86 ++++++++++++++++++++++++++++++++++++++++ .ai/shared-guidelines.md | 1 + 2 files changed, 87 insertions(+) create mode 100644 .ai/agent-practices.md diff --git a/.ai/agent-practices.md b/.ai/agent-practices.md new file mode 100644 index 00000000..bdcc4227 --- /dev/null +++ b/.ai/agent-practices.md @@ -0,0 +1,86 @@ +# Working practices for AI agents + +Distilled, portable practices for any AI agent (Claude Code or otherwise) working in this +repo. Cross-references [shared-guidelines.md](../shared-guidelines.md) rather than repeating +it — see that file for worktree isolation (§3), no `Co-Authored-By` (§5), and not touching the +live app while a session is mid-run (§1, §2, §6). + +## 1. Never simulate behavior that did not happen + +A functional framing implying a step occurred requires that step to have actually occurred. +Never describe a test run, a check, or a fix that wasn't executed. If a command fails, report +the failure verbatim — don't paraphrase it into something that reads as success. + +## 2. HANDOFF — how agents report results + +Any sub-agent or automated task ends its final message with a structured block: + +```text +---HANDOFF--- +skill: +outcome: PASS | FAIL | PARTIAL | REWORK +- What was done/changed (cite files when relevant) +- Key decisions and why +- What remains / blockers / next steps +--- +``` + +- `skill:` and `outcome:` are the first two lines, always present, machine-parseable. +- 3-5 bullets, ≤150 words total. Be specific — no "made progress on X". +- **Out-of-scope discoveries go in the HANDOFF as a note, not as a fix.** Don't scope-creep a + task because you spotted something else wrong nearby. + +### Final-message contract + +Whoever dispatches an agent only sees that agent's **last message**. Earlier messages +(intermediate findings, a verification run, a report) are not reliably visible to the caller. +Consequences: + +- The HANDOFF block must be in the FINAL message, after any verification step — not before it. +- A long-form deliverable (a review, a research writeup) gets **written to a file** under + `.work-files//` and the path is cited in the HANDOFF; the HANDOFF itself stays short. +- **If the task ends in a commit + push, the push is the terminal action** — run it, then + confirm with `git log origin/ --oneline -1` that the remote head advanced, and only + then emit the HANDOFF. Never end on a sub-step (e.g. a formatting/lint pass) that leaves the + push undone — that sub-step's own output would become the final message instead. + +## 3. Review loop before shipping + +A change ships once an implementer ↔ reviewer loop has converged: the reviewer has nothing +left to flag (no unresolved correctness findings), not just "looks fine on a skim". A reviewer +verifies claims with **executed evidence** — actually running the command, the test, the +`git log`/`git show`/`wc -l` check — rather than re-reading the diff and trusting the prose. +Findings get fixed, then re-reviewed, until the loop is clean. + +## 4. Shell pitfalls in an agent harness + +Commands that read fine to a human can silently misbehave or get blocked in an agent +execution environment. Rules of thumb: + +- **No heredocs** (`<< EOF`) — treat multi-line content as data to write to a file, not to + inline into a shell command. +- **No `cd && git ...`** and **no `git -C ...`** for a repo other than the + current one — these patterns are commonly blocked by permission guards against + cross-repo/bare-repo mistakes. If you need another repo's state, `git fetch` into the + current one, or work from a separate worktree/checkout. +- **Avoid shell loops** (`for`/`while`/`until`) in a single command — prefer separate + commands, or `xargs` for a single-command iteration. +- **Use `jq` for JSON parsing**, not inline `python3 -c "import json; ..."` — cleaner and less + fragile to quote. +- **Use `.work-files/`, not `/tmp/`, for scratch files** — it's gitignored, project-scoped, and + visible in the Work Files sidebar tab for debugging. +- **No `sleep N && command` to wait for background work** — if a task runs in the background, + react to its completion signal; don't poll with a fixed sleep. + +## 5. Memory / notes hygiene + +Any saved note, memory, or prior observation is a **point-in-time snapshot**, not live state. +Before citing something more than about a week old as a current fact — a file's line count, a +commit range, a "this is safe" claim — re-verify it against the actual repo (read the file, run +the check). If it's stale, correct or delete it in the same pass rather than repeating it. + +## 6. Scope discipline + +Fix exactly what was asked. When a task names N specific findings, apply N fixes — no +unrelated cleanup, no rewriting adjacent prose "while you're in there". If you notice something +else that's wrong, say so in the HANDOFF; don't fix it silently. diff --git a/.ai/shared-guidelines.md b/.ai/shared-guidelines.md index 3ee29209..47b9b207 100644 --- a/.ai/shared-guidelines.md +++ b/.ai/shared-guidelines.md @@ -16,6 +16,7 @@ Switchboard is an **Electron desktop app**: renderer + main-process, no Domain/A | Change Plans/Memory/.work-files panels (CodeMirror) | [contexts/viewer-panel.md](contexts/viewer-panel.md) | | Change the renderer (sidebar, terminal, app.js) | `public/*.js` — entry is `app.js` | | Write a test | `test/*.test.js` — node:test + jsdom for renderer files | +| Working practices for AI agents (HANDOFF format, shell pitfalls, review loop) | [agent-practices.md](agent-practices.md) | For a guided tour of the codebase architecture, start at [contexts/README.md](contexts/README.md).