From f5e59d245fd0e8fc66abf0fefba663c299e4a0a5 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Fri, 15 May 2026 16:48:17 -0700 Subject: [PATCH 01/24] Rebrand homepage and metadata to Dormouse MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hero copy: - Hook: "So many terminals. Which one needs attention?" - Reveal: "A dormouse / knows when / to wake up." - Footnote: "Multitasking terminal for mice (and hotkey wizards too)" (demoted from headline to footnote) Body copy MouseTerm → Dormouse throughout Home, SiteHeader brand text, Dependencies copy, NotifySignupForm button ("Tether ships" → "Pocket ships"). Coming-next section reframed as Dormouse Pocket; image alt updated. Install steps and artifact filenames flipped to Dormouse-*. Marketplace URLs point to diffplug.dormouse (publish target). HTML head + og:/twitter: meta refreshed with new title, description, site_name, canonical, and dormouse.sh URLs. Co-Authored-By: Claude Opus 4.7 (1M context) --- website/index.html | 26 +++++------ website/src/components/NotifySignupForm.tsx | 2 +- website/src/components/SiteHeader.tsx | 2 +- website/src/pages/Dependencies.tsx | 2 +- website/src/pages/Home.tsx | 48 ++++++++++----------- 5 files changed, 40 insertions(+), 40 deletions(-) diff --git a/website/index.html b/website/index.html index fca90f9b..3cf42a6b 100644 --- a/website/index.html +++ b/website/index.html @@ -4,30 +4,30 @@ - MouseTerm — The multitasking terminal for mice - + Dormouse — A dormouse knows when to wake up + - + - - - - - + + + + + - + - - - - + + + + diff --git a/website/src/components/NotifySignupForm.tsx b/website/src/components/NotifySignupForm.tsx index bb49d4d6..3916f56a 100644 --- a/website/src/components/NotifySignupForm.tsx +++ b/website/src/components/NotifySignupForm.tsx @@ -63,7 +63,7 @@ export function NotifySignupForm() { type="submit" className="min-h-12 inline-flex items-center justify-center rounded-md border border-[var(--color-caramel)] bg-[var(--color-caramel)]/15 px-6 py-3 text-base font-display text-[var(--color-caramel)] transition hover:bg-[var(--color-caramel)]/25 sm:w-auto" > - Notify me when Tether ships + Notify me when Pocket ships {message && ( diff --git a/website/src/components/SiteHeader.tsx b/website/src/components/SiteHeader.tsx index 9cd28fae..03de70de 100644 --- a/website/src/components/SiteHeader.tsx +++ b/website/src/components/SiteHeader.tsx @@ -90,7 +90,7 @@ const SiteHeader = forwardRef( } } > - MouseTerm + Dormouse
{controls ?
{controls}
: null} diff --git a/website/src/pages/Dependencies.tsx b/website/src/pages/Dependencies.tsx index 3814cbf7..33751501 100644 --- a/website/src/pages/Dependencies.tsx +++ b/website/src/pages/Dependencies.tsx @@ -12,7 +12,7 @@ export function Component() { Dependencies

- MouseTerm (standalone app and VS Code plugin) has {deps.length} transitive dependencies. Thank you to every author and contributor. + Dormouse (standalone app and VS Code plugin) has {deps.length} transitive dependencies. Thank you to every author and contributor.

Thanks also to ascii-splash and react-router and their transitive dependencies, which we use for this marketing page but are not part of the end-user application. diff --git a/website/src/pages/Home.tsx b/website/src/pages/Home.tsx index 92bafdca..f48e527f 100644 --- a/website/src/pages/Home.tsx +++ b/website/src/pages/Home.tsx @@ -129,15 +129,15 @@ const INSTALL_STEPS: Record - Too many terminals. - Not enough focus. + So many terminals. + Which one needs attention?

{/* Hero words — crossfade in place with the hook, just below the header */} -
+
- Multitasking + A dormouse - Terminal + knows when - for Mice + to wake up.

- (and hotkey wizards too) + Multitasking terminal for mice (and hotkey wizards too)

@@ -739,7 +739,7 @@ function Home() {

Stop watching terminals spin

- MouseTerm tracks activity the same way you do — visual motion. When a + Dormouse tracks activity the same way you do — visual motion. When a pane stops changing for two seconds, it marks the task complete and alerts you.

@@ -762,7 +762,7 @@ function Home() { doesn't copy; it asks your program to kill itself.

- MouseTerm lets you copy paste like a human, not a terminal. + Dormouse lets you copy paste like a human, not a terminal.

@@ -787,8 +787,8 @@ function Home() {
-

Get MouseTerm

-

The multitasking terminal for mice.

+

Get Dormouse

+

A dormouse knows when to wake up. Multitasking terminal for mice.

} @@ -803,7 +803,7 @@ function Home() {

Also works in Cursor, Windsurf, Antigravity, or any other VS Code fork.

} peek="marketplace" variant="wide" @@ -811,7 +811,7 @@ function Home() { Visual Studio Marketplace } peek="openVsx" variant="wide" @@ -884,7 +884,7 @@ function Home() {
MouseTerm Tether running on a phone
@@ -892,10 +892,10 @@ function Home() { Walk away. Keep going.

- Coming next: Tether. Pair a - terminal session to your phone over WebRTC and take a stroll, the MouseTerm alert - system will buzz you if there's anything to do. A hosted auto-pairing service comes - later — just leave and keep working, no "I'm walking away" dance. + Coming next: Dormouse Pocket. + Tether a terminal session to your phone over WebRTC and take a stroll — the Dormouse + alert system buzzes you if there's anything to do. A hosted auto-pairing service comes + later, so you can just leave and keep working, no "I'm walking away" dance.

Open source and free to self-host, or pay us a little bit and you can use ours. We'll discount for early adopters, so don't miss out! From 5b299e249f227c38e15842251b3f6459af99c039 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Fri, 15 May 2026 16:50:10 -0700 Subject: [PATCH 02/24] =?UTF-8?q?Rename=20Tether=20page/route=20=E2=86=92?= =?UTF-8?q?=20Pocket?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - pages/Tether.tsx → pages/Pocket.tsx - Route /tether → /pocket - All internal symbols (Tether* → Pocket*, TETHER_* → POCKET_*) and CSS body classes (tether-marketing-body → pocket-marketing-body, tether-terminal-body → pocket-terminal-body) - Share title, aria-label, and page body updated to "Dormouse Pocket" - "Tether" kept as the verb in body copy ("Tether a terminal session to your phone…") Co-Authored-By: Claude Opus 4.7 (1M context) --- website/src/App.tsx | 4 +- website/src/index.css | 8 +-- website/src/pages/{Tether.tsx => Pocket.tsx} | 60 ++++++++++---------- 3 files changed, 36 insertions(+), 36 deletions(-) rename website/src/pages/{Tether.tsx => Pocket.tsx} (86%) diff --git a/website/src/App.tsx b/website/src/App.tsx index 97eaa9b8..94fba271 100644 --- a/website/src/App.tsx +++ b/website/src/App.tsx @@ -10,8 +10,8 @@ export const routes: RouteRecord[] = [ lazy: () => import("./pages/Playground"), }, { - path: "/tether", - lazy: () => import("./pages/Tether"), + path: "/pocket", + lazy: () => import("./pages/Pocket"), }, { path: "/changelog", diff --git a/website/src/index.css b/website/src/index.css index 16134f97..baa4414c 100644 --- a/website/src/index.css +++ b/website/src/index.css @@ -36,19 +36,19 @@ html body { height: auto; } -html body.tether-marketing-body { +html body.pocket-marketing-body { overflow: auto; } -body.tether-marketing-body #root { +body.pocket-marketing-body #root { height: auto; } -html body.tether-terminal-body { +html body.pocket-terminal-body { overflow: hidden; } -body.tether-terminal-body #root { +body.pocket-terminal-body #root { height: 100vh; } diff --git a/website/src/pages/Tether.tsx b/website/src/pages/Pocket.tsx similarity index 86% rename from website/src/pages/Tether.tsx rename to website/src/pages/Pocket.tsx index d3c58b8e..80e58229 100644 --- a/website/src/pages/Tether.tsx +++ b/website/src/pages/Pocket.tsx @@ -16,13 +16,13 @@ import { TutorialState } from "../lib/tutorial-state"; import { BUSY_DEMO_DURATION_MS, BUSY_DEMO_INTERVAL_MS, TutRunner } from "../lib/tut-runner"; import { ChangelogRunner } from "../lib/changelog-runner"; -export { Tether as Component }; +export { Pocket as Component }; type FakePtyAdapter = import("mouseterm-lib/lib/platform/fake-adapter").FakePtyAdapter; -const TETHER_PANE = "tether-ascii-splash"; -const TETHER_THEME_ID = "vscode.theme-kimbie-dark.kimbie-dark"; -const TETHER_SESSIONS: MobileWallSession[] = [{ id: TETHER_PANE, title: "ascii-splash" }]; +const POCKET_PANE = "pocket-ascii-splash"; +const POCKET_THEME_ID = "vscode.theme-kimbie-dark.kimbie-dark"; +const POCKET_SESSIONS: MobileWallSession[] = [{ id: POCKET_PANE, title: "ascii-splash" }]; function useIsMobileViewport() { const [isMobile, setIsMobile] = useState(false); @@ -38,32 +38,32 @@ function useIsMobileViewport() { return isMobile; } -function useTetherTheme() { +function usePocketTheme() { const restoredRef = useRef(false); if (!restoredRef.current) { - restoreActiveTheme(TETHER_THEME_ID); + restoreActiveTheme(POCKET_THEME_ID); restoredRef.current = true; } } -function TetherTerminalExperience({ +function PocketTerminalExperience({ interactive, fillViewport = false, }: { interactive: boolean; fillViewport?: boolean; }) { - useTetherTheme(); + usePocketTheme(); const [terminalReady, setTerminalReady] = useState(false); const adapterRef = useRef(null); const shellRegistryRef = useRef(null); const autoStartedRef = useRef>(new Set()); const spawnUnsubRef = useRef<(() => void) | null>(null); const busyDemoDisposeRef = useRef<(() => void) | null>(null); - const [activePaneId, setActivePaneId] = useState(TETHER_PANE); + const [activePaneId, setActivePaneId] = useState(POCKET_PANE); const [touchMode, setTouchMode] = useState("gestures"); const [keyboardMode, setKeyboardMode] = useState("type"); - const sessionItems = useMobileWallSessionItems(TETHER_SESSIONS, activePaneId); + const sessionItems = useMobileWallSessionItems(POCKET_SESSIONS, activePaneId); const mouseStates = useSyncExternalStore( subscribeToMouseSelection, getMouseSelectionSnapshot, @@ -74,7 +74,7 @@ function TetherTerminalExperience({ && activeMouseState.mouseReporting !== "none"; const tryAutoStart = useCallback((id: string) => { - if (id !== TETHER_PANE) return; + if (id !== POCKET_PANE) return; if (autoStartedRef.current.has(id)) return; const shellRegistry = shellRegistryRef.current; if (!shellRegistry) return; @@ -99,7 +99,7 @@ function TetherTerminalExperience({ registry.initAlertStateReceiver(); adapterRef.current = adapter; adapter.setDefaultScenario(scenarios.SCENARIO_SHELL_PROMPT); - adapter.setScenario(TETHER_PANE, { name: "none", chunks: [] }); + adapter.setScenario(POCKET_PANE, { name: "none", chunks: [] }); const tutorialState = new TutorialState(); const shellRegistry = new PlaygroundShellRegistry( @@ -137,13 +137,13 @@ function TetherTerminalExperience({ }, ); shellRegistryRef.current = shellRegistry; - shellRegistry.ensureShell(TETHER_PANE); + shellRegistry.ensureShell(POCKET_PANE); spawnUnsubRef.current = adapter.onPtySpawn(({ id }) => { shellRegistry.ensureShell(id); tryAutoStart(id); }); - if (adapter.hasPty(TETHER_PANE)) tryAutoStart(TETHER_PANE); + if (adapter.hasPty(POCKET_PANE)) tryAutoStart(POCKET_PANE); setTerminalReady(true); } @@ -177,7 +177,7 @@ function TetherTerminalExperience({ terminal={ terminalReady ? ( setKeyboardMode("sessions")} @@ -202,12 +202,12 @@ function TetherTerminalExperience({ ); } -function MobileTetherPage() { +function MobilePocketPage() { return (

- +
- +
); @@ -220,7 +220,7 @@ function ShareUrlButton() { const url = window.location.href; if (navigator.share) { try { - await navigator.share({ url, title: "MouseTerm Tether" }); + await navigator.share({ url, title: "Dormouse Pocket" }); return; } catch (err) { if ((err as DOMException)?.name === "AbortError") return; @@ -248,13 +248,13 @@ function ShareUrlButton() { ); } -function DesktopTetherPage() { +function DesktopPocketPage() { return (
} + controls={} />
@@ -267,9 +267,9 @@ function DesktopTetherPage() { to try it out! (WIP)

- Pair a terminal session to your phone over WebRTC and take a stroll, the MouseTerm alert - system will buzz you if there's anything to do. A hosted auto-pairing service comes - later — just leave and keep working, no "I'm walking away" dance. + Tether a terminal session to your phone over WebRTC and take a stroll — the Dormouse + alert system buzzes you if there's anything to do. A hosted auto-pairing service comes + later, so you can just leave and keep working, no "I'm walking away" dance.

Open source and free to self-host, or pay us a little bit and you can use ours. We'll discount for early adopters, so don't miss out! @@ -277,12 +277,12 @@ function DesktopTetherPage() {

-
+
@@ -292,14 +292,14 @@ function DesktopTetherPage() { ); } -function Tether() { +function Pocket() { const isMobile = useIsMobileViewport(); useEffect(() => { - const className = isMobile ? "tether-terminal-body" : "tether-marketing-body"; + const className = isMobile ? "pocket-terminal-body" : "pocket-marketing-body"; document.body.classList.add(className); return () => document.body.classList.remove(className); }, [isMobile]); - return isMobile ? : ; + return isMobile ? : ; } From ecd59a1c548dc7b7f37477db3980ec9426ec3d8b Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Fri, 15 May 2026 16:56:36 -0700 Subject: [PATCH 03/24] Rebrand VS Code extension to Dormouse MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vscode-ext/package.json: - name: mouseterm → dormouse - displayName: "Dormouse — Terminal Multiplexer" (descriptive suffix for marketplace SEO; brand is "Dormouse" elsewhere) - description rewritten with persistence + alert framing - homepage → dormouse.sh - All command IDs mouseterm.* → dormouse.* - Command titles "MouseTerm: …" → "Dormouse: …" - View container + view IDs (mouseterm-panel/view → dormouse-panel/view) - activationEvents updated - Keywords: added "dormouse" and "persistent" - vsix output filename + dogfood script names vscode-ext/src/*.ts: - MouseTermViewProvider → DormouseViewProvider - All command registrations and the executeCommand("…view.focus") call updated to dormouse.* IDs - Webview panel serializer + createWebviewPanel viewType: mouseterm → dormouse - Warning messages "MouseTerm:" → "Dormouse:" - Output channel name → "Dormouse" - Storage keys: mouseterm.session, mouseterm.selectedShellPath → dormouse.session, dormouse.selectedShellPath (userbase=0, no migration) Cross-cutting message-type rename (extension ↔ webview wire protocol): - All `mouseterm:*` ExtensionMessage / WebviewMessage discriminators → `dormouse:*` (init, saveState, flushSessionSave[Done], newTerminal, selectedShell, openThemeDebugger) - Lib side: vscode-adapter.ts (+ test), Wall.tsx window event ("mouseterm:new-terminal" → "dormouse:new-terminal"), ThemeDebugger OPEN_THEME_DEBUGGER_EVENT constant Lib-side miscellany: - themes/store.ts localStorage keys ("mouseterm:installed-themes/active-theme" → "dormouse:…") - ansi.ts playground PROMPT visual: user@mouseterm → user@dormouse - KillModal story content + Playground.tsx comment matched - shell-defaults.ts comment Internal TS type `MouseTermTheme` left as-is — purely internal, 27-site cascade, no user-facing impact. Separate refactor. Co-Authored-By: Claude Opus 4.7 (1M context) --- lib/src/components/ThemeDebugger.tsx | 2 +- lib/src/components/Wall.tsx | 4 +- lib/src/lib/ansi.ts | 6 +-- lib/src/lib/platform/vscode-adapter.test.ts | 4 +- lib/src/lib/platform/vscode-adapter.ts | 18 +++---- lib/src/lib/shell-defaults.ts | 2 +- lib/src/lib/themes/store.ts | 4 +- lib/src/stories/KillModal.stories.tsx | 2 +- vscode-ext/package.json | 56 +++++++++++---------- vscode-ext/src/extension.ts | 46 ++++++++--------- vscode-ext/src/log.ts | 2 +- vscode-ext/src/message-router.ts | 14 +++--- vscode-ext/src/message-types.ts | 14 +++--- vscode-ext/src/session-state.ts | 2 +- vscode-ext/src/shell-selection.ts | 2 +- vscode-ext/src/webview-view-provider.ts | 4 +- website/src/pages/Playground.tsx | 2 +- 17 files changed, 93 insertions(+), 91 deletions(-) diff --git a/lib/src/components/ThemeDebugger.tsx b/lib/src/components/ThemeDebugger.tsx index 9e9f5352..c939c893 100644 --- a/lib/src/components/ThemeDebugger.tsx +++ b/lib/src/components/ThemeDebugger.tsx @@ -8,7 +8,7 @@ import { type VscodeThemeVarTraceOrigin, } from '../lib/themes'; -export const OPEN_THEME_DEBUGGER_EVENT = 'mouseterm:openThemeDebugger'; +export const OPEN_THEME_DEBUGGER_EVENT = 'dormouse:openThemeDebugger'; export function openThemeDebugger(): void { window.dispatchEvent(new CustomEvent(OPEN_THEME_DEBUGGER_EVENT)); diff --git a/lib/src/components/Wall.tsx b/lib/src/components/Wall.tsx index 584dc849..edd07c1d 100644 --- a/lib/src/components/Wall.tsx +++ b/lib/src/components/Wall.tsx @@ -595,8 +595,8 @@ export function Wall({ showShellSpawnNotice(newId, `Opened ${shellName}`); } }; - window.addEventListener('mouseterm:new-terminal', handler); - return () => window.removeEventListener('mouseterm:new-terminal', handler); + window.addEventListener('dormouse:new-terminal', handler); + return () => window.removeEventListener('dormouse:new-terminal', handler); }, [generatePaneId, selectPane, showShellSpawnNotice]); const addSplitPanel = useCallback(( diff --git a/lib/src/lib/ansi.ts b/lib/src/lib/ansi.ts index a34d0abd..1d3ecbe0 100644 --- a/lib/src/lib/ansi.ts +++ b/lib/src/lib/ansi.ts @@ -23,10 +23,10 @@ export const LEAVE_ALT_SCREEN = `${CLEAR_SCREEN}${CURSOR_HOME}${ESC}?25h${ESC}?1 // SGR mouse-reporting toggles. xterm parses these and the wall's // mouse-mode-observer flips the cursor-icon override on/off so the user -// knows MouseTerm is "trapping the mouse" while the program runs. +// knows Dormouse is "trapping the mouse" while the program runs. export const MOUSE_ENABLE = `${ESC}?1000h${ESC}?1002h${ESC}?1003h${ESC}?1006h`; export const MOUSE_DISABLE = `${ESC}?1003l${ESC}?1002l${ESC}?1000l${ESC}?1006l`; -// Stylized `user@mouseterm:~$ ` prompt used by the playground shell and +// Stylized `user@dormouse:~$ ` prompt used by the playground shell and // by canned scenarios so they look the same. -export const PROMPT = `${fg(32)}user${RESET}@${fg(36)}mouseterm${RESET}:${BOLD}${fg(34)}~${RESET}$ `; +export const PROMPT = `${fg(32)}user${RESET}@${fg(36)}dormouse${RESET}:${BOLD}${fg(34)}~${RESET}$ `; diff --git a/lib/src/lib/platform/vscode-adapter.test.ts b/lib/src/lib/platform/vscode-adapter.test.ts index c6271f35..2800c721 100644 --- a/lib/src/lib/platform/vscode-adapter.test.ts +++ b/lib/src/lib/platform/vscode-adapter.test.ts @@ -142,14 +142,14 @@ describe('VSCodeAdapter PTY exit handling', () => { it('forwards shell replacement requests from the extension host', () => { const requests: unknown[] = []; - windowTarget.addEventListener('mouseterm:new-terminal', (event) => { + windowTarget.addEventListener('dormouse:new-terminal', (event) => { requests.push((event as CustomEvent).detail); }); new VSCodeAdapter(); windowTarget.dispatchEvent(new MessageEvent('message', { data: { - type: 'mouseterm:newTerminal', + type: 'dormouse:newTerminal', shell: '/bin/zsh', args: ['-l'], name: 'zsh', diff --git a/lib/src/lib/platform/vscode-adapter.ts b/lib/src/lib/platform/vscode-adapter.ts index 0f271875..d22ec84e 100644 --- a/lib/src/lib/platform/vscode-adapter.ts +++ b/lib/src/lib/platform/vscode-adapter.ts @@ -60,7 +60,7 @@ export class VSCodeAdapter implements PlatformAdapter { } } else if (msg.type === 'terminal:semanticEvents') { applyTerminalSemanticEventsByPtyId(msg.id, msg.events ?? []); - } else if (msg.type === 'mouseterm:flushSessionSave') { + } else if (msg.type === 'dormouse:flushSessionSave') { for (const handler of this.flushRequestHandlers) { handler({ requestId: msg.requestId }); } @@ -75,8 +75,8 @@ export class VSCodeAdapter implements PlatformAdapter { attentionDismissedRing: msg.attentionDismissedRing, }); } - } else if (msg.type === 'mouseterm:newTerminal') { - window.dispatchEvent(new CustomEvent('mouseterm:new-terminal', { + } else if (msg.type === 'dormouse:newTerminal') { + window.dispatchEvent(new CustomEvent('dormouse:new-terminal', { detail: { shell: msg.shell, args: msg.args, @@ -85,10 +85,10 @@ export class VSCodeAdapter implements PlatformAdapter { announce: msg.announce, }, })); - } else if (msg.type === 'mouseterm:selectedShell') { + } else if (msg.type === 'dormouse:selectedShell') { setDefaultShellOpts(msg.shell ? { shell: msg.shell, args: msg.args } : null); - } else if (msg.type === 'mouseterm:openThemeDebugger') { - window.dispatchEvent(new CustomEvent('mouseterm:openThemeDebugger')); + } else if (msg.type === 'dormouse:openThemeDebugger') { + window.dispatchEvent(new CustomEvent('dormouse:openThemeDebugger')); } }); } @@ -194,7 +194,7 @@ export class VSCodeAdapter implements PlatformAdapter { } requestInit(): void { - this.vscode.postMessage({ type: 'mouseterm:init' }); + this.vscode.postMessage({ type: 'dormouse:init' }); } onPtyList(handler: (detail: { ptys: PtyInfo[] }) => void): void { @@ -222,7 +222,7 @@ export class VSCodeAdapter implements PlatformAdapter { } notifySessionFlushComplete(requestId: string): void { - this.vscode.postMessage({ type: 'mouseterm:flushSessionSaveDone', requestId }); + this.vscode.postMessage({ type: 'dormouse:flushSessionSaveDone', requestId }); } // --- Alert management (proxied to extension host) --- @@ -284,7 +284,7 @@ export class VSCodeAdapter implements PlatformAdapter { saveState(state: unknown): void { this.hostState = state; this.vscode.setState(state); - this.vscode.postMessage({ type: 'mouseterm:saveState', state }); + this.vscode.postMessage({ type: 'dormouse:saveState', state }); } getState(): unknown { diff --git a/lib/src/lib/shell-defaults.ts b/lib/src/lib/shell-defaults.ts index b8cabab6..7a3f57f3 100644 --- a/lib/src/lib/shell-defaults.ts +++ b/lib/src/lib/shell-defaults.ts @@ -1,7 +1,7 @@ // Shared "currently selected" shell, used when spawning without an explicit // choice (e.g. a keyboard-driven split). Seeded before standalone Wall mount, // updated by AppBar's ShellDropdown, and updated by the VSCode extension -// pushing mouseterm:selectedShell. +// pushing dormouse:selectedShell. // // Extracted into its own module to avoid circular dependencies between // terminal-registry and platform/vscode-adapter. diff --git a/lib/src/lib/themes/store.ts b/lib/src/lib/themes/store.ts index a45732d7..f6cf95e3 100644 --- a/lib/src/lib/themes/store.ts +++ b/lib/src/lib/themes/store.ts @@ -3,8 +3,8 @@ import type { MouseTermTheme } from './types'; import _bundledThemes from './bundled.json'; const bundledThemes = _bundledThemes as unknown as MouseTermTheme[]; -const INSTALLED_KEY = 'mouseterm:installed-themes'; -const ACTIVE_KEY = 'mouseterm:active-theme'; +const INSTALLED_KEY = 'dormouse:installed-themes'; +const ACTIVE_KEY = 'dormouse:active-theme'; function getStorage(): Storage | null { const storage = globalThis.localStorage; diff --git a/lib/src/stories/KillModal.stories.tsx b/lib/src/stories/KillModal.stories.tsx index 2d53763d..40d7881f 100644 --- a/lib/src/stories/KillModal.stories.tsx +++ b/lib/src/stories/KillModal.stories.tsx @@ -6,7 +6,7 @@ function KillModal({ char = 'G', onCancel, exit }: { char?: string; onCancel?: (
{/* Simulated terminal content behind the overlay */}
-
user@mouseterm:~$ npm run build
+
user@dormouse:~$ npm run build
Building project...
{/* Kill confirmation overlay — positioned over the pane */} diff --git a/vscode-ext/package.json b/vscode-ext/package.json index 9d0aabd1..b313f6a6 100644 --- a/vscode-ext/package.json +++ b/vscode-ext/package.json @@ -1,12 +1,12 @@ { - "name": "mouseterm", - "displayName": "MouseTerm", - "description": "Multitasking terminal with tmux keybindings, mouse support, and a built-in alert system for completed tasks and prompts.", + "name": "dormouse", + "displayName": "Dormouse — Terminal Multiplexer", + "description": "A persistent multitasking terminal — tmux keybindings, mouse support, and a built-in alert system that buzzes you when builds, agents, or scripts finish.", "version": "0.9.1", "publisher": "diffplug", "license": "FSL-1.1-MIT", "icon": "icon.png", - "homepage": "https://mouseterm.com/", + "homepage": "https://dormouse.sh/", "repository": { "type": "git", "url": "https://github.com/diffplug/mouseterm" @@ -22,75 +22,77 @@ "terminal", "multiplexer", "mouse", + "dormouse", "ai", "agent", "alert", + "persistent", "split", "panes", "completion" ], "activationEvents": [ - "onView:mouseterm.view", - "onWebviewPanel:mouseterm" + "onView:dormouse.view", + "onWebviewPanel:dormouse" ], "main": "./dist/extension.js", "contributes": { "commands": [ { - "command": "mouseterm.focus", - "title": "MouseTerm: Focus", + "command": "dormouse.focus", + "title": "Dormouse: Focus", "icon": { "light": "icon-tiny-light.png", "dark": "icon-tiny-dark.png" } }, { - "command": "mouseterm.open", - "title": "MouseTerm: Open in Editor" + "command": "dormouse.open", + "title": "Dormouse: Open in Editor" }, { - "command": "mouseterm.debugTheme", - "title": "MouseTerm: Debug Theme" + "command": "dormouse.debugTheme", + "title": "Dormouse: Debug Theme" }, { - "command": "mouseterm.newTerminal", - "title": "MouseTerm: New Terminal", + "command": "dormouse.newTerminal", + "title": "Dormouse: New Terminal", "icon": "$(add)" }, { - "command": "mouseterm.selectShell", - "title": "MouseTerm: Select Shell", + "command": "dormouse.selectShell", + "title": "Dormouse: Select Shell", "icon": "$(gear)" } ], "menus": { "view/title": [ { - "command": "mouseterm.selectShell", + "command": "dormouse.selectShell", "group": "navigation@1", - "when": "view == mouseterm.view" + "when": "view == dormouse.view" }, { - "command": "mouseterm.newTerminal", + "command": "dormouse.newTerminal", "group": "navigation@2", - "when": "view == mouseterm.view" + "when": "view == dormouse.view" } ] }, "viewsContainers": { "panel": [ { - "id": "mouseterm-panel", - "title": "MouseTerm", + "id": "dormouse-panel", + "title": "Dormouse", "icon": "$(terminal)" } ] }, "views": { - "mouseterm-panel": [ + "dormouse-panel": [ { - "id": "mouseterm.view", - "name": "MouseTerm", + "id": "dormouse.view", + "name": "Dormouse", "type": "webview" } ] @@ -101,8 +103,8 @@ "build:frontend": "vite build --config vite.config.ts", "build": "esbuild src/extension.ts --bundle --outdir=dist --external:vscode --external:node-pty --format=cjs --platform=node && esbuild src/pty-host.js --bundle --outfile=dist/pty-host.js --external:node-pty --format=cjs --platform=node && cp -RL node_modules/node-pty dist/node-pty", "watch": "pnpm build --watch", - "package": "vsce package --no-dependencies --out mouseterm.vsix", - "dogfood": "pnpm package && code --install-extension mouseterm.vsix --force && rm -f mouseterm.vsix && echo '\\n✦ Reload VSCode window (Cmd+Shift+P → Reload Window) to pick up the new extension.'", + "package": "vsce package --no-dependencies --out dormouse.vsix", + "dogfood": "pnpm package && code --install-extension dormouse.vsix --force && rm -f dormouse.vsix && echo '\\n✦ Reload VSCode window (Cmd+Shift+P → Reload Window) to pick up the new extension.'", "publish:marketplace": "vsce publish --no-dependencies", "publish:openvsx": "ovsx publish --no-dependencies" }, diff --git a/vscode-ext/src/extension.ts b/vscode-ext/src/extension.ts index 912f0f5f..8c25cc0f 100644 --- a/vscode-ext/src/extension.ts +++ b/vscode-ext/src/extension.ts @@ -1,7 +1,7 @@ import * as vscode from 'vscode'; import * as path from 'path'; import * as ptyManager from './pty-manager'; -import { MouseTermViewProvider } from './webview-view-provider'; +import { DormouseViewProvider } from './webview-view-provider'; import { attachRouter, flushAllSessions, getAlertStates } from './message-router'; import { getWebviewHtml } from './webview-html'; import { log } from './log'; @@ -10,7 +10,7 @@ import { readPersistedSession } from '../../lib/src/lib/session-types'; import { resolveSelectedShell, setSelectedShellPath, getSelectedShellPath } from './shell-selection'; import type { ExtensionMessage } from './message-types'; -type NewTerminalMessage = Extract; +type NewTerminalMessage = Extract; let extensionContext: vscode.ExtensionContext | null = null; @@ -19,7 +19,7 @@ let extensionContext: vscode.ExtensionContext | null = null; * * @param savedState Per-panel state. For `deserializeWebviewPanel` this is the * state VS Code preserved from the panel's `vscode.setState()`; for a fresh - * panel opened via `mouseterm.open` this is `undefined`. + * panel opened via `dormouse.open` this is `undefined`. */ function setupPanel( context: vscode.ExtensionContext, @@ -63,7 +63,7 @@ export function activate(context: vscode.ExtensionContext) { extensionContext = context; ptyManager.setExtensionPath(context.extensionPath); - const provider = new MouseTermViewProvider(context); + const provider = new DormouseViewProvider(context); // Updates the shell-derived state in one place: the view header (shell // name appears next to the title via description) and the webview's @@ -74,13 +74,13 @@ export function activate(context: vscode.ExtensionContext) { }; const postNewTerminal = async (message: Omit) => { - await vscode.commands.executeCommand('mouseterm.view.focus'); + await vscode.commands.executeCommand('dormouse.view.focus'); for (const delay of [0, 50, 200]) { if (delay > 0) { await new Promise((resolve) => setTimeout(resolve, delay)); } const posted = await provider.postMessage({ - type: 'mouseterm:newTerminal', + type: 'dormouse:newTerminal', ...message, }); if (posted) return true; @@ -97,25 +97,25 @@ export function activate(context: vscode.ExtensionContext) { }); context.subscriptions.push( - vscode.window.registerWebviewViewProvider('mouseterm.view', provider, { + vscode.window.registerWebviewViewProvider('dormouse.view', provider, { // Keep the webview script + xterm DOM alive when the Panel is hidden // (close/toggle), so PTYs and scrollback are preserved across re-show // without going through the reconnect dance. webviewOptions: { retainContextWhenHidden: true }, }), - vscode.window.registerWebviewPanelSerializer('mouseterm', { + vscode.window.registerWebviewPanelSerializer('dormouse', { async deserializeWebviewPanel(panel: vscode.WebviewPanel, state: unknown) { setupPanel(context, panel, state, () => provider.getSelectedShell()); }, }), - vscode.commands.registerCommand('mouseterm.focus', () => { - vscode.commands.executeCommand('mouseterm.view.focus'); + vscode.commands.registerCommand('dormouse.focus', () => { + vscode.commands.executeCommand('dormouse.view.focus'); }), - vscode.commands.registerCommand('mouseterm.open', () => { + vscode.commands.registerCommand('dormouse.open', () => { const mediaPath = path.join(context.extensionPath, 'media'); const panel = vscode.window.createWebviewPanel( - 'mouseterm', - 'MouseTerm', + 'dormouse', + 'Dormouse', vscode.ViewColumn.Active, { enableScripts: true, @@ -125,18 +125,18 @@ export function activate(context: vscode.ExtensionContext) { ); setupPanel(context, panel, undefined, () => provider.getSelectedShell()); }), - vscode.commands.registerCommand('mouseterm.debugTheme', async () => { - await vscode.commands.executeCommand('mouseterm.view.focus'); + vscode.commands.registerCommand('dormouse.debugTheme', async () => { + await vscode.commands.executeCommand('dormouse.view.focus'); for (const delay of [0, 50, 200]) { if (delay > 0) { await new Promise((resolve) => setTimeout(resolve, delay)); } - const posted = await provider.postMessage({ type: 'mouseterm:openThemeDebugger' }); + const posted = await provider.postMessage({ type: 'dormouse:openThemeDebugger' }); if (posted) return; } - void vscode.window.showWarningMessage('MouseTerm: open the MouseTerm view before debugging the theme.'); + void vscode.window.showWarningMessage('Dormouse: open the Dormouse view before debugging the theme.'); }), - vscode.commands.registerCommand('mouseterm.newTerminal', async () => { + vscode.commands.registerCommand('dormouse.newTerminal', async () => { const shells = await ptyManager.getAvailableShells(); const shell = resolveSelectedShell(context, shells); const posted = await postNewTerminal({ @@ -145,13 +145,13 @@ export function activate(context: vscode.ExtensionContext) { name: shell?.name, }); if (!posted) { - void vscode.window.showWarningMessage('MouseTerm: open the MouseTerm view before creating a terminal.'); + void vscode.window.showWarningMessage('Dormouse: open the Dormouse view before creating a terminal.'); } }), - vscode.commands.registerCommand('mouseterm.selectShell', async () => { + vscode.commands.registerCommand('dormouse.selectShell', async () => { const shells = await ptyManager.getAvailableShells(); if (shells.length === 0) { - void vscode.window.showWarningMessage('MouseTerm: no shells detected.'); + void vscode.window.showWarningMessage('Dormouse: no shells detected.'); return; } const currentPath = getSelectedShellPath(context) ?? shells[0].path; @@ -163,7 +163,7 @@ export function activate(context: vscode.ExtensionContext) { args: s.args, })); const picked = await vscode.window.showQuickPick(items, { - title: 'Select default shell for MouseTerm', + title: 'Select default shell for Dormouse', placeHolder: 'Changing this opens a matching terminal; new panes reuse it.', }); if (!picked) return; @@ -193,7 +193,7 @@ export function activate(context: vscode.ExtensionContext) { announce: true, }); if (!posted) { - void vscode.window.showWarningMessage('MouseTerm: open the MouseTerm view before changing the active terminal type.'); + void vscode.window.showWarningMessage('Dormouse: open the Dormouse view before changing the active terminal type.'); } } }), diff --git a/vscode-ext/src/log.ts b/vscode-ext/src/log.ts index b9880db4..a10620b4 100644 --- a/vscode-ext/src/log.ts +++ b/vscode-ext/src/log.ts @@ -5,7 +5,7 @@ let channel: vscode.OutputChannel | null = null; export const log = { init() { if (!channel) { - channel = vscode.window.createOutputChannel('MouseTerm'); + channel = vscode.window.createOutputChannel('Dormouse'); } }, info(...args: unknown[]) { diff --git a/vscode-ext/src/message-router.ts b/vscode-ext/src/message-router.ts index a61c01ff..2fad7967 100644 --- a/vscode-ext/src/message-router.ts +++ b/vscode-ext/src/message-router.ts @@ -118,7 +118,7 @@ export function attachRouter( let disposed = false; // Webview-facing subscriptions — only active when the webview has live content. - // Subscribed on mouseterm:init, unsubscribed when webview content is gone. + // Subscribed on dormouse:init, unsubscribed when webview content is gone. let disconnectWebview: (() => void) | null = null; function claim(id: string): void { @@ -160,13 +160,13 @@ export function attachRouter( timeout, }); - void webview.postMessage({ type: 'mouseterm:flushSessionSave', requestId } satisfies ExtensionMessage); + void webview.postMessage({ type: 'dormouse:flushSessionSave', requestId } satisfies ExtensionMessage); }); } /** * Subscribe PTY data and alert state forwarding to the webview. - * Called when the webview sends mouseterm:init (proving it has live content). + * Called when the webview sends dormouse:init (proving it has live content). * Returns a cleanup function that unsubscribes everything. */ function connectWebview(): () => void { @@ -270,7 +270,7 @@ export function attachRouter( webview.postMessage({ type: 'clipboard:image', path: null, requestId: msg.requestId } satisfies ExtensionMessage); }); break; - case 'mouseterm:init': { + case 'dormouse:init': { // Webview has (re-)initialized — subscribe to live events. // Tear down previous subscriptions first (webview was destroyed and recreated). disconnectWebview?.(); @@ -281,7 +281,7 @@ export function attachRouter( const selected = options?.getSelectedShell?.(); if (selected) { webview.postMessage({ - type: 'mouseterm:selectedShell', + type: 'dormouse:selectedShell', shell: selected.shell, args: selected.args, } satisfies ExtensionMessage); @@ -368,10 +368,10 @@ export function attachRouter( } break; } - case 'mouseterm:flushSessionSaveDone': + case 'dormouse:flushSessionSaveDone': resolveFlushRequest(msg.requestId); break; - case 'mouseterm:saveState': + case 'dormouse:saveState': options?.onSaveState?.(msg.state); break; diff --git a/vscode-ext/src/message-types.ts b/vscode-ext/src/message-types.ts index 6d0b22c7..b49e8b1c 100644 --- a/vscode-ext/src/message-types.ts +++ b/vscode-ext/src/message-types.ts @@ -12,9 +12,9 @@ export type WebviewMessage = | { type: 'pty:getShells'; requestId?: string } | { type: 'clipboard:readFiles'; requestId: string } | { type: 'clipboard:readImage'; requestId: string } - | { type: 'mouseterm:init' } - | { type: 'mouseterm:saveState'; state: unknown } - | { type: 'mouseterm:flushSessionSaveDone'; requestId: string } + | { type: 'dormouse:init' } + | { type: 'dormouse:saveState'; state: unknown } + | { type: 'dormouse:flushSessionSaveDone'; requestId: string } // Alert actions | { type: 'alert:remove'; id: string } | { type: 'alert:toggle'; id: string } @@ -47,16 +47,16 @@ export type ExtensionMessage = | { type: 'clipboard:files'; paths: string[] | null; requestId: string } | { type: 'clipboard:image'; path: string | null; requestId: string } | { - type: 'mouseterm:newTerminal'; + type: 'dormouse:newTerminal'; shell?: string; args?: string[]; name?: string; replaceUntouched?: boolean; announce?: boolean; } - | { type: 'mouseterm:selectedShell'; shell?: string; args?: string[] } - | { type: 'mouseterm:openThemeDebugger' } - | { type: 'mouseterm:flushSessionSave'; requestId: string } + | { type: 'dormouse:selectedShell'; shell?: string; args?: string[] } + | { type: 'dormouse:openThemeDebugger' } + | { type: 'dormouse:flushSessionSave'; requestId: string } // Alert state updates | { type: 'alert:state'; diff --git a/vscode-ext/src/session-state.ts b/vscode-ext/src/session-state.ts index 22b77e70..6fa612a4 100644 --- a/vscode-ext/src/session-state.ts +++ b/vscode-ext/src/session-state.ts @@ -4,7 +4,7 @@ import type { AlertState } from '../../lib/src/lib/alert-manager'; import { readPersistedSession, type PersistedAlertState, type PersistedPane, type PersistedSession } from '../../lib/src/lib/session-types'; import { log } from './log'; -const SESSION_STATE_KEY = 'mouseterm.session'; +const SESSION_STATE_KEY = 'dormouse.session'; export function getSavedSessionState(context: vscode.ExtensionContext): PersistedSession | null { const saved = readPersistedSession(context.workspaceState.get(SESSION_STATE_KEY)); diff --git a/vscode-ext/src/shell-selection.ts b/vscode-ext/src/shell-selection.ts index 7fe861c9..f765ee62 100644 --- a/vscode-ext/src/shell-selection.ts +++ b/vscode-ext/src/shell-selection.ts @@ -6,7 +6,7 @@ export interface ShellEntry { args: string[]; } -const KEY = 'mouseterm.selectedShellPath'; +const KEY = 'dormouse.selectedShellPath'; export function getSelectedShellPath(context: vscode.ExtensionContext): string | undefined { return context.workspaceState.get(KEY) ?? context.globalState.get(KEY); diff --git a/vscode-ext/src/webview-view-provider.ts b/vscode-ext/src/webview-view-provider.ts index a05d95a6..6418ec4f 100644 --- a/vscode-ext/src/webview-view-provider.ts +++ b/vscode-ext/src/webview-view-provider.ts @@ -8,7 +8,7 @@ import * as ptyManager from './pty-manager'; import { resolveSelectedShell } from './shell-selection'; import { log } from './log'; -export class MouseTermViewProvider implements vscode.WebviewViewProvider { +export class DormouseViewProvider implements vscode.WebviewViewProvider { private view: vscode.WebviewView | undefined; private routerDisposable: vscode.Disposable | undefined; private description: string | undefined; @@ -28,7 +28,7 @@ export class MouseTermViewProvider implements vscode.WebviewViewProvider { setSelectedShell(opts: { shell?: string; args?: string[] } | null): void { this.selectedShell = opts; void this.postMessage({ - type: 'mouseterm:selectedShell', + type: 'dormouse:selectedShell', shell: opts?.shell, args: opts?.args, }); diff --git a/website/src/pages/Playground.tsx b/website/src/pages/Playground.tsx index 8ea587aa..9c83dfdb 100644 --- a/website/src/pages/Playground.tsx +++ b/website/src/pages/Playground.tsx @@ -84,7 +84,7 @@ function Playground() { adapter.setDefaultScenario(scenarios.SCENARIO_SHELL_PROMPT); // Each runner-owned pane suppresses the default shell-prompt scenario, - // otherwise spawnPty queues a delayed `user@mouseterm:~$` write that + // otherwise spawnPty queues a delayed `user@dormouse:~$` write that // would land in the runner's alt-screen and corrupt its output. for (const pane of panes) { adapter.setScenario(pane.id, { name: "none", chunks: [] }); From 7bed9eee858433e248e797913802a6766bf4cf8e Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Fri, 15 May 2026 17:01:24 -0700 Subject: [PATCH 04/24] Rebrand standalone (Tauri) app to Dormouse MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tauri.conf.json: - productName: MouseTerm → Dormouse (this is what produces Dormouse.app / Dormouse-windows-x64-setup.exe / Dormouse-linux-x86_64.AppImage on next bundle build) - identifier: com.mouseterm.standalone → sh.dormouse.standalone (userbase=0, no migration concern) - Window title: Dormouse - Updater endpoint URL → dormouse.sh Rust crate rename (Cargo.toml): - [package] name: mouseterm → dormouse - [package] description rewritten - [lib] name: mouseterm_lib → dormouse_lib - main.rs entry call: dormouse_lib::run() - lib.rs: log dir / log file names (~/Library/Logs/Dormouse/dormouse.log, %LOCALAPPDATA%/Dormouse/dormouse.log, /tmp/dormouse.log fallback) - lib.rs: window event "mouseterm://files-dropped" → "dormouse://files-dropped" - lib.rs: panic message + temp-dir test prefix - Test fixture paths (\\Users\\…\\Local\\Dormouse\\…) Frontend / Tauri JS bridge: - standalone/index.html - AppBar.tsx: window event "mouseterm:new-terminal" → "dormouse:new-terminal" - tauri-adapter.ts: matching files-dropped listener, STATE_KEY ("mouseterm.session" → "dormouse.session") - updater.ts: STORAGE_KEY ("dormouse:update-result"), changelog URL (mouseterm.com → dormouse.sh); GitHub repo URL left as-is (repo not renamed in this pass) - updater.test.ts: matching STORAGE_KEY + changelog URL expectations Capability description: - standalone/src-tauri/capabilities/default.json scripts/dogfood.sh: - Install dirs ($LOCALAPPDATA/Dormouse, /Applications/Dormouse.app) - Bundle filename patterns (Dormouse_*-setup.exe, Dormouse_*.dmg) - Binary names ($RELEASE_DIR/dormouse, dormouse.exe) - PowerShell process filter -Name dormouse,node - The `pnpm --filter mouseterm-standalone` workspace filter stays — npm package names are internal infrastructure, not renamed in this pass Internal npm/pnpm workspace package names left as-is (mouseterm-lib, mouseterm-standalone, mouseterm-sidecar) — purely internal, no user-facing impact; separate refactor. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --- standalone/index.html | 2 +- standalone/scripts/dogfood.sh | 26 +++++++++---------- standalone/src-tauri/Cargo.toml | 6 ++--- .../src-tauri/capabilities/default.json | 2 +- standalone/src-tauri/src/lib.rs | 22 ++++++++-------- standalone/src-tauri/src/main.rs | 2 +- standalone/src-tauri/tauri.conf.json | 8 +++--- standalone/src/AppBar.tsx | 2 +- standalone/src/tauri-adapter.ts | 4 +-- standalone/src/updater.test.ts | 4 +-- standalone/src/updater.ts | 4 +-- 11 files changed, 41 insertions(+), 41 deletions(-) diff --git a/standalone/index.html b/standalone/index.html index 42d08170..bb127d78 100644 --- a/standalone/index.html +++ b/standalone/index.html @@ -3,7 +3,7 @@ <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> - <title>MouseTerm + Dormouse
diff --git a/standalone/scripts/dogfood.sh b/standalone/scripts/dogfood.sh index 49361a97..bcff5a66 100755 --- a/standalone/scripts/dogfood.sh +++ b/standalone/scripts/dogfood.sh @@ -41,23 +41,23 @@ if [[ "${1:-}" == "--install" ]]; then # Platform-specific: copy built files to system install location case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*|Windows_NT) - INSTALL_DIR="$LOCALAPPDATA/MouseTerm" + INSTALL_DIR="$LOCALAPPDATA/Dormouse" if [[ ! -f "$INSTALL_DIR/uninstall.exe" ]]; then - echo "MouseTerm is not installed yet." + echo "Dormouse is not installed yet." echo "Run the installer once first:" - echo " $RELEASE_DIR/bundle/nsis/MouseTerm_*-setup.exe" + echo " $RELEASE_DIR/bundle/nsis/Dormouse_*-setup.exe" echo "" echo "After that, 'dogfood:standalone --install' will work from then on." exit 1 fi - # Kill any running MouseTerm processes (the app + its sidecar node.exe, + # Kill any running Dormouse processes (the app + its sidecar node.exe, # plus orphan sidecars from a prior run) before we overwrite their files. # We can't use `taskkill //IM node.exe` here: that matches every node.exe # on the system, including the pnpm process that invoked this script, # and `//T` would then cascade and kill us. Filter by image path so we # only target processes loaded from the install dir. powershell.exe -NoProfile -Command \ - "Get-Process -Name mouseterm,node -EA SilentlyContinue | Where-Object Path -Like '$LOCALAPPDATA\\MouseTerm\\*' | Stop-Process -Force -EA SilentlyContinue" \ + "Get-Process -Name dormouse,node -EA SilentlyContinue | Where-Object Path -Like '$LOCALAPPDATA\\Dormouse\\*' | Stop-Process -Force -EA SilentlyContinue" \ >/dev/null 2>&1 || true # Wipe install-dir contents except uninstall.exe (managed by NSIS). # We delete *contents* rather than the directory itself so we don't trip @@ -65,23 +65,23 @@ if [[ "${1:-}" == "--install" ]]; then # an exe image from it. find "$INSTALL_DIR" -mindepth 1 -maxdepth 1 -not -name 'uninstall.exe' \ -exec rm -rf {} + - cp "$RELEASE_DIR/mouseterm.exe" "$INSTALL_DIR/" + cp "$RELEASE_DIR/dormouse.exe" "$INSTALL_DIR/" cp "$RELEASE_DIR/node.exe" "$INSTALL_DIR/" cp -r "$RELEASE_DIR/_up_/" "$INSTALL_DIR/_up_/" echo "✦ Installed to $INSTALL_DIR" ;; Darwin) - INSTALL_DIR="/Applications/MouseTerm.app" + INSTALL_DIR="/Applications/Dormouse.app" if [[ ! -d "$INSTALL_DIR" ]]; then - echo "MouseTerm is not installed yet." + echo "Dormouse is not installed yet." echo "Install via the DMG first:" - echo " open $RELEASE_DIR/bundle/dmg/MouseTerm_*.dmg" + echo " open $RELEASE_DIR/bundle/dmg/Dormouse_*.dmg" echo "" echo "After that, 'dogfood:standalone --install' will work from then on." exit 1 fi rm -rf "$INSTALL_DIR" - cp -r "$RELEASE_DIR/bundle/macos/MouseTerm.app" "$INSTALL_DIR" + cp -r "$RELEASE_DIR/bundle/macos/Dormouse.app" "$INSTALL_DIR" echo "✦ Installed to $INSTALL_DIR" ;; *) @@ -93,11 +93,11 @@ else # --- Launch mode (default) --- case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*|Windows_NT) - "$RELEASE_DIR/mouseterm.exe" ;; + "$RELEASE_DIR/dormouse.exe" ;; Darwin) - "$RELEASE_DIR/mouseterm" ;; + "$RELEASE_DIR/dormouse" ;; Linux) - "$RELEASE_DIR/mouseterm" ;; + "$RELEASE_DIR/dormouse" ;; *) echo "Unsupported platform: $(uname -s)" exit 1 ;; diff --git a/standalone/src-tauri/Cargo.toml b/standalone/src-tauri/Cargo.toml index 50de2082..48769ed1 100644 --- a/standalone/src-tauri/Cargo.toml +++ b/standalone/src-tauri/Cargo.toml @@ -1,7 +1,7 @@ [package] -name = "mouseterm" +name = "dormouse" version = "0.9.1" -description = "Mouse-friendly multitasking terminal" +description = "Persistent multitasking terminal for mice (and hotkey wizards too)" authors = ["DiffPlug"] license = "FSL-1.1-MIT" edition = "2021" @@ -10,7 +10,7 @@ edition = "2021" global-min-publish-age = "14 days" [lib] -name = "mouseterm_lib" +name = "dormouse_lib" crate-type = ["lib", "cdylib", "staticlib"] [build-dependencies] diff --git a/standalone/src-tauri/capabilities/default.json b/standalone/src-tauri/capabilities/default.json index 4d2ae78d..92d765a4 100644 --- a/standalone/src-tauri/capabilities/default.json +++ b/standalone/src-tauri/capabilities/default.json @@ -1,6 +1,6 @@ { "identifier": "default", - "description": "Default capability set for MouseTerm", + "description": "Default capability set for Dormouse", "windows": ["main"], "permissions": [ "core:app:allow-version", diff --git a/standalone/src-tauri/src/lib.rs b/standalone/src-tauri/src/lib.rs index b3a2384c..62e24fa1 100644 --- a/standalone/src-tauri/src/lib.rs +++ b/standalone/src-tauri/src/lib.rs @@ -54,11 +54,11 @@ fn default_log_path() -> PathBuf { #[cfg(target_os = "windows")] if let Some(local_app_data) = env::var_os("LOCALAPPDATA") { return PathBuf::from(local_app_data) - .join("MouseTerm") - .join("mouseterm.log"); + .join("Dormouse") + .join("dormouse.log"); } - env::temp_dir().join("mouseterm.log") + env::temp_dir().join("dormouse.log") } fn log_path() -> &'static Path { @@ -100,7 +100,7 @@ fn init_log() { { let _ = writeln!( file, - "[{}] MouseTerm log started at {}", + "[{}] Dormouse log started at {}", log_timestamp(), path.display() ); @@ -606,7 +606,7 @@ pub fn run() { .iter() .map(|p| p.to_string_lossy().into_owned()) .collect(); - let _ = window.emit("mouseterm://files-dropped", serde_json::json!({ "paths": payload })); + let _ = window.emit("dormouse://files-dropped", serde_json::json!({ "paths": payload })); } }) .setup(|app| { @@ -648,7 +648,7 @@ pub fn run() { read_update_log, ]) .build(tauri::generate_context!()) - .expect("error while building MouseTerm") + .expect("error while building Dormouse") .run(|app, event| { if let RunEvent::Exit = event { if let Some(state) = app.try_state::() { @@ -674,7 +674,7 @@ mod tests { .duration_since(UNIX_EPOCH) .expect("system time before unix epoch") .as_nanos(); - let path = std::env::temp_dir().join(format!("mouseterm-{name}-{suffix}")); + let path = std::env::temp_dir().join(format!("dormouse-{name}-{suffix}")); fs::create_dir_all(&path).expect("failed to create temp dir"); TempDir(path) } @@ -737,24 +737,24 @@ mod tests { #[test] fn strips_windows_verbatim_prefix_for_node_main_script() { let path = strip_windows_verbatim_prefix( - r"\\?\C:\Users\EdgarTwigg\AppData\Local\MouseTerm\_up_\sidecar\main.js", + r"\\?\C:\Users\EdgarTwigg\AppData\Local\Dormouse\_up_\sidecar\main.js", ) .expect("expected verbatim path to be stripped"); assert_eq!( path, - PathBuf::from(r"C:\Users\EdgarTwigg\AppData\Local\MouseTerm\_up_\sidecar\main.js") + PathBuf::from(r"C:\Users\EdgarTwigg\AppData\Local\Dormouse\_up_\sidecar\main.js") ); } #[test] fn strips_windows_verbatim_unc_prefix_for_node_main_script() { - let path = strip_windows_verbatim_prefix(r"\\?\UNC\server\share\MouseTerm\sidecar\main.js") + let path = strip_windows_verbatim_prefix(r"\\?\UNC\server\share\Dormouse\sidecar\main.js") .expect("expected verbatim UNC path to be stripped"); assert_eq!( path, - PathBuf::from(r"\\server\share\MouseTerm\sidecar\main.js") + PathBuf::from(r"\\server\share\Dormouse\sidecar\main.js") ); } diff --git a/standalone/src-tauri/src/main.rs b/standalone/src-tauri/src/main.rs index d3149f99..8c7cbec4 100644 --- a/standalone/src-tauri/src/main.rs +++ b/standalone/src-tauri/src/main.rs @@ -1,5 +1,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] fn main() { - mouseterm_lib::run(); + dormouse_lib::run(); } diff --git a/standalone/src-tauri/tauri.conf.json b/standalone/src-tauri/tauri.conf.json index 32cd7672..abce3b8c 100644 --- a/standalone/src-tauri/tauri.conf.json +++ b/standalone/src-tauri/tauri.conf.json @@ -1,8 +1,8 @@ { "$schema": "https://schema.tauri.app/config/2", - "productName": "MouseTerm", + "productName": "Dormouse", "version": "0.9.1", - "identifier": "com.mouseterm.standalone", + "identifier": "sh.dormouse.standalone", "build": { "beforeDevCommand": "pnpm dev", "devUrl": "http://localhost:1420", @@ -12,7 +12,7 @@ "app": { "windows": [ { - "title": "MouseTerm", + "title": "Dormouse", "titleBarStyle": "Overlay", "hiddenTitle": true, "width": 1200, @@ -47,7 +47,7 @@ "updater": { "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEFDNUE3RThENTQxQTY0REIKUldUYlpCcFVqWDVhckxRQjBFbGw4anhJMUZ5L2VEU0pGNTluS1hPR0F1OGc1T3BUYTVjbHd0WG0K", "endpoints": [ - "https://mouseterm.com/standalone-latest.json" + "https://dormouse.sh/standalone-latest.json" ], "windows": { "installMode": "passive" diff --git a/standalone/src/AppBar.tsx b/standalone/src/AppBar.tsx index 0a38240d..ac89a035 100644 --- a/standalone/src/AppBar.tsx +++ b/standalone/src/AppBar.tsx @@ -109,7 +109,7 @@ function ShellDropdown({ shells }: { shells: ShellEntry[] }) { shell: ShellEntry, options: { replaceUntouched?: boolean; announce?: boolean } = {}, ) => { - window.dispatchEvent(new CustomEvent('mouseterm:new-terminal', { + window.dispatchEvent(new CustomEvent('dormouse:new-terminal', { detail: { shell: shell.path, args: shell.args, diff --git a/standalone/src/tauri-adapter.ts b/standalone/src/tauri-adapter.ts index 34c0215f..5705620e 100644 --- a/standalone/src/tauri-adapter.ts +++ b/standalone/src/tauri-adapter.ts @@ -107,7 +107,7 @@ export class TauriAdapter implements PlatformAdapter { // Inert while dragDropEnabled=false in tauri.conf.json. See diffplug/mouseterm#38 and tauri-apps/tauri#14373. this.unlistenFns.push( - await listen<{ paths: string[] }>("mouseterm://files-dropped", (event) => { + await listen<{ paths: string[] }>("dormouse://files-dropped", (event) => { const paths = event.payload.paths ?? []; if (paths.length === 0) return; for (const handler of this.filesDroppedHandlers) handler(paths); @@ -282,7 +282,7 @@ export class TauriAdapter implements PlatformAdapter { // --- State persistence --- - private static STATE_KEY = 'mouseterm.session'; + private static STATE_KEY = 'dormouse.session'; saveState(state: unknown): void { try { diff --git a/standalone/src/updater.test.ts b/standalone/src/updater.test.ts index 3576720e..1d3a81bb 100644 --- a/standalone/src/updater.test.ts +++ b/standalone/src/updater.test.ts @@ -36,7 +36,7 @@ vi.mock('@tauri-apps/api/core', () => ({ // --- Helpers --- -const STORAGE_KEY = 'mouseterm:update-result'; +const STORAGE_KEY = 'dormouse:update-result'; function makeUpdate(version = '0.5.0') { return { @@ -269,7 +269,7 @@ describe('updater', () => { openChangelog(); await vi.advanceTimersByTimeAsync(0); - expect(mocks.shellOpen).toHaveBeenCalledWith('https://mouseterm.com/changelog/after/0.4.0'); + expect(mocks.shellOpen).toHaveBeenCalledWith('https://dormouse.sh/changelog/after/0.4.0'); }); }); diff --git a/standalone/src/updater.ts b/standalone/src/updater.ts index 469c0615..cb54144f 100644 --- a/standalone/src/updater.ts +++ b/standalone/src/updater.ts @@ -15,7 +15,7 @@ function openUrl(url: string, context: string): void { // --- State --- -const STORAGE_KEY = 'mouseterm:update-result'; +const STORAGE_KEY = 'dormouse:update-result'; let state: UpdateBannerState = { status: 'idle' }; let availableUpdate: Update | null = null; @@ -65,7 +65,7 @@ export function openChangelog(): void { async function openCurrentVersionChangelog(): Promise { const version = (await getVersion()).trim(); - openUrl(`https://mouseterm.com/changelog/after/${encodeURIComponent(version)}`, 'changelog'); + openUrl(`https://dormouse.sh/changelog/after/${encodeURIComponent(version)}`, 'changelog'); } export async function buildDebugReport(error: string, toVersion: string): Promise { From 335165f67d030c74d000113c0cbff546ee04c814 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Fri, 15 May 2026 17:03:12 -0700 Subject: [PATCH 05/24] Update release pipeline for Dormouse artifact filenames MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit .github/workflows/release.yml: - Upload path for inner binary: mouseterm.exe → dormouse.exe - VS Code build pnpm filter: --filter mouseterm → --filter dormouse (matches the new vscode-ext/package.json name) - `--filter mouseterm-lib` workspace filter unchanged (internal pkg name) scripts/sign-and-deploy.sh: - FNAME_WIN/MAC/LINUX renamed: MouseTerm-* → Dormouse-* - Windows inner-exe lookup accepts both Dormouse.exe / dormouse.exe - Comment about AppleDouble extraction updated scripts/bump-version.sh: - Comment refs to the Cargo.lock `mouseterm` entry → `dormouse` website/public/standalone-latest.json left unchanged: it points to the already-published v0.9.1 release whose artifacts were uploaded as MouseTerm-*. sign-and-deploy.sh regenerates this file on the next release; the new entries will use Dormouse-*. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/release.yml | 6 +++--- scripts/bump-version.sh | 4 ++-- scripts/sign-and-deploy.sh | 10 +++++----- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ee9d3a61..52e4a133 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -80,7 +80,7 @@ jobs: with: name: ${{ matrix.artifact-name }} path: | - standalone/src-tauri/target/${{ matrix.target }}/release/mouseterm.exe + standalone/src-tauri/target/${{ matrix.target }}/release/dormouse.exe standalone/src-tauri/target/${{ matrix.target }}/release/bundle/**/*.exe standalone/src-tauri/target/${{ matrix.target }}/release/bundle/**/*.msi standalone/src-tauri/target/${{ matrix.target }}/release/bundle/**/*.dmg @@ -112,10 +112,10 @@ jobs: run: pnpm --filter mouseterm-lib test - name: Build frontend for VSCode - run: pnpm --filter mouseterm build:frontend + run: pnpm --filter dormouse build:frontend - name: Build extension - run: pnpm --filter mouseterm build + run: pnpm --filter dormouse build - name: Package extension run: cd vscode-ext && npx vsce package --no-dependencies diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh index 572400ba..92715e1a 100755 --- a/scripts/bump-version.sh +++ b/scripts/bump-version.sh @@ -5,7 +5,7 @@ set -euo pipefail # Bump version across all release artifacts and sync Cargo.lock. # ============================================================================= # Edits the four version files in lockstep, then runs cargo so Cargo.lock's -# `mouseterm` entry follows along. Print a diff stat for review. +# `dormouse` entry follows along. Print a diff stat for review. # # Usage: ./scripts/bump-version.sh # Example: ./scripts/bump-version.sh 0.9.0 @@ -53,7 +53,7 @@ for f in "$TAURI_CONF" "$VSCODE_PKG" "$LIB_PKG"; do done # Sync Cargo.lock by running cargo. `cargo check` is idempotent and updates -# the lockfile's mouseterm entry to match the bumped Cargo.toml. Without this, +# the lockfile's dormouse entry to match the bumped Cargo.toml. Without this, # Cargo.lock keeps the old version and ships out of sync with the binary. echo "Syncing Cargo.lock (cargo check)…" ( cd standalone/src-tauri && cargo check --offline >/dev/null ) diff --git a/scripts/sign-and-deploy.sh b/scripts/sign-and-deploy.sh index df5daa61..5d2fb194 100755 --- a/scripts/sign-and-deploy.sh +++ b/scripts/sign-and-deploy.sh @@ -47,9 +47,9 @@ TSA_URL="http://ts.ssl.com" GITHUB_REPO="diffplug/mouseterm" # Stable filenames for release assets (update bundles only) -FNAME_WIN="MouseTerm-windows-x64-setup.exe" -FNAME_MAC="MouseTerm-macos-aarch64.tar.gz" -FNAME_LINUX="MouseTerm-linux-x86_64.AppImage" +FNAME_WIN="Dormouse-windows-x64-setup.exe" +FNAME_MAC="Dormouse-macos-aarch64.tar.gz" +FNAME_LINUX="Dormouse-linux-x86_64.AppImage" # ============================================================================= # Helper Functions @@ -496,7 +496,7 @@ notarize_macos() { # COPYFILE_DISABLE=1 stops macOS's tar from writing ._* AppleDouble # sidecar files (resource-fork metadata) into the archive. Without # this the Tauri updater's extraction fails with - # `failed to unpack ._MouseTerm.app`. + # `failed to unpack ._Dormouse.app`. COPYFILE_DISABLE=1 tar -czf "$SIGN_DIR/$FNAME_MAC" -C "$(dirname "$app")" "$app_name" # Defense in depth: if any ._* slipped in anyway, fail loudly here @@ -521,7 +521,7 @@ sign_windows() { # Find the inner exe local exe_path - exe_path=$(find "$SIGN_DIR/standalone-win-x64" \( -name "MouseTerm.exe" -o -name "mouseterm.exe" \) -not -name "*setup*" -not -name "*install*" | head -1) + exe_path=$(find "$SIGN_DIR/standalone-win-x64" \( -name "Dormouse.exe" -o -name "dormouse.exe" \) -not -name "*setup*" -not -name "*install*" | head -1) [[ -n "$exe_path" ]] || error "Windows executable not found" log "Signing inner executable: $exe_path" From 66d97407a7dcd93f2d519da30a7a94619e4fda8a Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Fri, 15 May 2026 17:04:56 -0700 Subject: [PATCH 06/24] Rewrite README.md and vscode-ext/README.md for Dormouse MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root README: - Title and tagline reframed: "A dormouse knows when to wake up. Multitasking terminal for mice (and hotkey wizards too)." - Opening paragraph re-anchors with the homepage hook ("So many terminals — which one needs attention?") - Try-it links updated to dormouse.sh and the new marketplace IDs - Added a "Pocket (coming soon)" bullet to the features list - Folder structure description: website builds dormouse.sh vscode-ext/README.md: - Title and all body copy MouseTerm → Dormouse - All URLs mouseterm.com → dormouse.sh - Command palette entries: "Dormouse: Focus" / "Dormouse: Open in Editor" GitHub repo URL still points at diffplug/mouseterm (not renamed in this pass; GitHub redirects renamed repos so this is forward-compatible). Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 24 +++++++++++++----------- vscode-ext/README.md | 34 +++++++++++++++++----------------- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index c253c524..9704cd4d 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,19 @@ -# MouseTerm +# Dormouse -**Multitasking terminal for the mouse, tmux-compatible.** +**A dormouse knows when to wake up. Multitasking terminal for mice (and hotkey wizards too).** -Run multiple terminals side-by-side, click to split, drag to resize. -When a pane stops outputting for two seconds, it's marked done — works -with any CLI tool, no plugins or config. +So many terminals — which one needs attention? Dormouse tracks activity the +way you do: visual motion. When a pane stops changing for two seconds, +it's marked done. Works with any CLI tool that prints to a terminal — +no plugins, no configuration. -![MouseTerm hero](website/src/assets/video-climb-blink-and-stare.webp) +![Dormouse hero](website/src/assets/video-climb-blink-and-stare.webp) ## Try it -- **[Playground](https://mouseterm.com/playground)** — try in your browser, no install -- **[Demo videos and downloads](https://mouseterm.com)** — Mac, Windows, Linux -- **[Marketplace](https://marketplace.visualstudio.com/items?itemName=diffplug.mouseterm)** / **[Open VSX](https://open-vsx.org/extension/diffplug/mouseterm)** — VS Code extension (also works in Cursor, Windsurf, Antigravity) +- **[Playground](https://dormouse.sh/playground)** — try in your browser, no install +- **[Demo videos and downloads](https://dormouse.sh)** — Mac, Windows, Linux +- **[Marketplace](https://marketplace.visualstudio.com/items?itemName=diffplug.dormouse)** / **[Open VSX](https://open-vsx.org/extension/diffplug/dormouse)** — VS Code extension (also works in Cursor, Windsurf, Antigravity) ## Features @@ -22,6 +23,7 @@ with any CLI tool, no plugins or config. - **Copy-paste that works.** Click and drag selects text the way you'd expect, even in mouse-aware TUIs that normally swallow it as escape codes. Ctrl+C copies; killing the program is a separate gesture. - **Sleep/wake panes.** Minimize a terminal to a compact status indicator. It keeps running and you can still see whether its task finished. - **Dual distribution.** Standalone desktop app (Mac/Windows/Linux) or VS Code extension. +- **Pocket (coming soon).** Tether your sessions to your phone over WebRTC — walk away, keep working. ## Development @@ -49,7 +51,7 @@ pnpm test # runs all tests | Path | Description | |------|-------------| | `lib/` | Shared terminal library | -| `website/` | mouseterm.com (including playground) | +| `website/` | dormouse.sh (including playground) | | `standalone/` | Tauri desktop app | | `vscode-ext/` | VSCode extension | @@ -61,4 +63,4 @@ This project was built with a combination of Claude, Codex, and Devin. Recommend [FSL-1.1-MIT](LICENSE) — Copyright 2026 DiffPlug LLC -[Production dependencies](https://mouseterm.com/dependencies) +[Production dependencies](https://dormouse.sh/dependencies) diff --git a/vscode-ext/README.md b/vscode-ext/README.md index c5217ca5..86b8ccc2 100644 --- a/vscode-ext/README.md +++ b/vscode-ext/README.md @@ -1,17 +1,17 @@ -# MouseTerm +# Dormouse -Terminal multiplexer for VS Code (or [standalone app](https://mouseterm.com/#download)) - tmux keybindings, mouse support, human-friendly copy-paste, and alerts for completed tasks. +Terminal multiplexer for VS Code (or [standalone app](https://dormouse.sh/#download)) — tmux keybindings, mouse support, human-friendly copy-paste, and alerts for completed tasks. -[mouseterm.com/playground](https://mouseterm.com/playground) - try before you install +[dormouse.sh/playground](https://dormouse.sh/playground) — try before you install TODO: Hero GIF. ## Alert System -MouseTerm can WATCH a pane the same way you do — visual motion. When a watched pane stops changing for two seconds, it marks the task complete and alerts you. Apps can also ask for attention with terminal notification protocols, and shell-integrated commands can alert when they finish after you stop paying attention. +Dormouse tracks activity the same way you do — visual motion. When a pane stops changing for two seconds, it marks the task complete and alerts you. Works with any CLI tool that prints to a terminal, no plugins or configuration. -- todo-disabled WATCHING disabled -- todo-enabled WATCHING enabled +- todo-disabled alerts disabled +- todo-enabled alerts enabled - todo-armed task is running, will send an alert when task completes - todo-ringing task is finished and needs your attention @@ -21,11 +21,11 @@ This lightweight TODO system remembers which tasks need follow-up so you don't h ## Mouse-Friendly Copy and Paste -When you copy-paste from a terminal, you are usually stuck with a bunch of newlines that you wouldn't get if you were copying from any other kind of program. MouseTerm can optionally remove these with `Copy Rewrapped`. +When you copy-paste from a terminal, you are usually stuck with a bunch of newlines that you wouldn't get if you were copying from any other kind of program. Dormouse can optionally remove these with `Copy Rewrapped`. copy-paste -For TUIs which register for xterm mouse interception (such as `htop` and `neovim`), most terminals make it impossible for you to copy using the mouse. MouseTerm makes it easy to temporarily override the mouse interception. +For TUIs which register for xterm mouse interception (such as `htop` and `neovim`), most terminals make it impossible for you to copy using the mouse. Dormouse makes it easy to temporarily override the mouse interception. TODO: GIF showing htop and the override mechanism @@ -43,7 +43,7 @@ TODO: layout GIF ## Keyboard Shortcuts -If you use the mouse, then MouseTerm is always in **passthrough** mode, where all keypresses passthrough to the selected terminal. If you press `LShift` followed by `RShift` in quick succession (or `LCmd → RCmd`, or `LCtrl → RCtrl`), then you will enter **command** mode where keypresses can spawn terminals, navigate panes, and rearrange the layout. +If you use the mouse, then Dormouse is always in **passthrough** mode, where all keypresses passthrough to the selected terminal. If you press `LShift` followed by `RShift` in quick succession (or `LCmd → RCmd`, or `LCtrl → RCtrl`), then you will enter **command** mode where keypresses can spawn terminals, navigate panes, and rearrange the layout. ### Command Mode Shortcuts @@ -62,24 +62,24 @@ If you use the mouse, then MouseTerm is always in **passthrough** mode, where al ## Any Theme, Anywhere -MouseTerm uses your VSCode theme — colors, styling, everything. Switch themes and MouseTerm switches with you. No separate configuration, no mismatched colors. +Dormouse uses your VSCode theme — colors, styling, everything. Switch themes and Dormouse switches with you. No separate configuration, no mismatched colors. -TODO: GIF showing theme switching — user changes VSCode theme and MouseTerm updates instantly to match +TODO: GIF showing theme switching — user changes VSCode theme and Dormouse updates instantly to match -You can also use MouseTerm in the Panel area (bottom, next to the built-in terminal), in the Editor area (center region where you edit files), or both. +You can also use Dormouse in the Panel area (bottom, next to the built-in terminal), in the Editor area (center region where you edit files), or both. -TODO: GIF showing MouseTerm in various areas +TODO: GIF showing Dormouse in various areas ## Getting Started 1. Install the extension 2. Open the command palette (`Cmd+Shift+P` / `Ctrl+Shift+P`) - - **MouseTerm: Focus** to open the "Panel" version of MouseTerm (next to the terminal) - - **MouseTerm: Open in Editor** to open a MouseTerm tab in the content area (you can open multiple) + - **Dormouse: Focus** to open the "Panel" version of Dormouse (next to the terminal) + - **Dormouse: Open in Editor** to open a Dormouse tab in the content area (you can open multiple) ## Links -- Prefer a standalone terminal app? Self-updating installers available for Win, Mac and Linux at [mouseterm.com](https://mouseterm.com/#download) -- You can try it in a [browser playground](https://mouseterm.com/playground) +- Prefer a standalone terminal app? Self-updating installers available for Win, Mac and Linux at [dormouse.sh](https://dormouse.sh/#download) +- You can try it in a [browser playground](https://dormouse.sh/playground) - [GitHub](https://github.com/diffplug/mouseterm) - Brought to you by [DiffPlug](https://www.diffplug.com/) From b67707a3f87602d03958ef86e32589be375e99ee Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Fri, 15 May 2026 17:10:49 -0700 Subject: [PATCH 07/24] Rename brand in internal docs, storybook, and remaining identifiers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docs: - AGENTS.md, DESIGN.md, PRODUCT.md: MouseTerm → Dormouse in prose - docs/specs/*.md: prose + URLs (mouseterm.com → dormouse.sh) + command IDs (mouseterm.* → dormouse.*) + message types (mouseterm:* → dormouse:*) + view container IDs to match code - docs/specs/deploy.md: example artifact filenames, log paths, env vars - docs/specs/glossary.md, shortcuts.md: brand prose - docs/specs/layout.md: `mousetermTheme` symbol → `dormouseTheme` - pnpm filter `--filter mouseterm` (vscode-ext build) updated to `--filter dormouse`. The `--filter mouseterm-lib` and `--filter mouseterm-standalone` filters stay (internal pkg names). Storybook fixtures: - UpdateDebugDialog.stories.tsx: example error path /Applications/Dormouse.app - Smoke.stories.tsx: "Dormouse Tokens" panel header / semantic-token caption - MobileTerminalUi.stories.tsx: TETHER_WALL_* → POCKET_WALL_*, story export name TetherWall → PocketWall, scenario id Code-side identifier renames discovered during doc sweep: - lib/src/components/Wall.tsx: `mousetermTheme` → `dormouseTheme` - standalone/src-tauri/src/lib.rs: `MOUSETERM_LOG_FILE` env var → `DORMOUSE_LOG_FILE` - website/src/lib/tutorial-state.ts: localStorage key "mouseterm-tut-v3" → "dormouse-tut-v3" (userbase=0, no migration) Left as-is: - CHANGELOG.md historical entries (per "leave historical changelog" guidance) - Internal npm workspace package names (mouseterm-lib, mouseterm-standalone, mouseterm-sidecar, mouseterm-website) — purely build-tool plumbing - github.com/diffplug/mouseterm URLs — repo not renamed in this pass - .claude/commands/release-notes.md — agent self-modification guard prevented edit; needs a manual MouseTerm → Dormouse pass Co-Authored-By: Claude Opus 4.7 (1M context) --- AGENTS.md | 6 +- DESIGN.md | 10 +- PRODUCT.md | 8 +- docs/specs/OSC.md | 20 +- docs/specs/alert.md | 1016 ++++------------- docs/specs/auto-update.md | 8 +- docs/specs/deploy.md | 34 +- docs/specs/glossary.md | 2 +- docs/specs/layout.md | 4 +- docs/specs/mobile-ui.md | 8 +- docs/specs/shortcuts.md | 4 +- docs/specs/terminal-state.md | 2 +- docs/specs/theme.md | 30 +- docs/specs/transport.md | 22 +- docs/specs/tutorial.md | 4 +- docs/specs/vscode.md | 76 +- lib/src/components/Wall.tsx | 4 +- lib/src/stories/MobileTerminalUi.stories.tsx | 22 +- lib/src/stories/Smoke.stories.tsx | 4 +- lib/src/stories/UpdateDebugDialog.stories.tsx | 2 +- standalone/src-tauri/src/lib.rs | 2 +- website/src/lib/tutorial-state.ts | 2 +- 22 files changed, 362 insertions(+), 928 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 1962f1a1..f7128ad0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,4 +1,4 @@ -# MouseTerm +# Dormouse A mouse-friendly multitasking terminal built with pnpm, react, typescript, vite, tailwind, storybook, and xterm.js. @@ -35,9 +35,9 @@ The primary job of a spec is to be an accurate reference for the current state o - **`docs/specs/alert.md`** — Activity monitoring state machine, alert trigger/clearing rules, attention model, TODO lifecycle, bell button visual states and interaction, door alert indicators, hardening (a11y, motion, i18n, overflow), notification protocols (`OSC 9` / `OSC 9;4` / `OSC 99` / `OSC 777` / `BEL`), the `ActivityNotification` model, notification text handling and security, and the notification preview/detail UI. Read this when touching: `activity-monitor.ts`, `alert-manager.ts`, `AlertManager` notification/progress paths, the alert bell or TODO pill in `Wall.tsx` (TerminalPaneHeader), alert indicators in `Door.tsx`, the `a`/`t` keyboard shortcuts, or TODO notification preview UI. Layout.md defers to this spec for all alert/TODO behavior. - **`docs/specs/terminal-state.md`** — Terminal semantic state for CWD, shell prompt/editing/running/finished lifecycle, command runs, terminal title fallback, normalized semantic OSC events (`OSC 7`, `OSC 9;9`, `OSC 133`, `OSC 633`, `OSC 1337`, `OSC 0/2`), title-candidate diagnostics, header derivation, and grouping keys. Read this when touching `terminal-state.ts`, `terminal-state-store.ts`, semantic event parsing in `terminal-protocol.ts`, adapter semantic event forwarding, or derived pane/door labels. - **`docs/specs/OSC.md`** — Registry of every supported OSC sequence with pointers to the spec defining its behavior (alert.md or terminal-state.md), the canonical parsing-location and `pty:data` strip semantics, iTerm2 self-identification (env vars, `CSI > q` response, fail-inertly rule), and known-unimplemented iTerm2 and clipboard-capable sequences. Read this when touching: OSC parsing at the PTY data boundary, the iTerm2 identity env vars (`TERM_PROGRAM`, `LC_TERMINAL`), or adding support for a new OSC sequence. -- **`docs/specs/transport.md`** — Adapter-agnostic protocol shared across VS Code, standalone, and fake adapters: PTY lifecycle (decoupled from webview), `replayChunks`/`scrollbackChunks` buffering, reconnection sequence (`mouseterm:init` → `pty:list` + `pty:replay`), the full webview ↔ host message protocol, persisted-session types, and universal invariants (shell login args, scrollback trailing newline, replay drop-replies-only). Read this when touching: `pty-manager.ts`, `pty-host.js`, `pty-core.js`, `message-router.ts`, `message-types.ts`, `vscode-adapter.ts`, `fake-adapter.ts`, `reconnect.ts`, `session-save.ts`, `session-restore.ts`, `session-types.ts`, or any code crossing the webview/host boundary. +- **`docs/specs/transport.md`** — Adapter-agnostic protocol shared across VS Code, standalone, and fake adapters: PTY lifecycle (decoupled from webview), `replayChunks`/`scrollbackChunks` buffering, reconnection sequence (`dormouse:init` → `pty:list` + `pty:replay`), the full webview ↔ host message protocol, persisted-session types, and universal invariants (shell login args, scrollback trailing newline, replay drop-replies-only). Read this when touching: `pty-manager.ts`, `pty-host.js`, `pty-core.js`, `message-router.ts`, `message-types.ts`, `vscode-adapter.ts`, `fake-adapter.ts`, `reconnect.ts`, `session-save.ts`, `session-restore.ts`, `session-types.ts`, or any code crossing the webview/host boundary. - **`docs/specs/vscode.md`** — VS Code-specific layer: hosting modes (WebviewView + WebviewPanel), extension manifest, VS Code persistence flow (`workspaceState`, `vscode.setState`, `WebviewPanelSerializer`, deactivate ordering, `mergeAlertStates` rule, `retainContextWhenHidden`), theme integration (`--vscode-*` → `--color-*` with the runtime resolver), CSP, build pipeline, and dream-architecture commands. The transport protocol it speaks (PTY lifecycle, message protocol, persisted-session types) lives in `transport.md`. Read this when touching: `extension.ts`, `webview-view-provider.ts`, `session-state.ts`, `webview-html.ts`, the theme resolver/observer in `terminal-theme.ts`, or VS Code commands and context keys. -- **`docs/specs/tutorial.md`** — Playground tutorial on the website: 3-pane layout, interactive `tut` TUI runner with three sections (keyboard navigation, alerts/TODOs, copy/paste), per-item detection wired to `WallEvent` / activity store / mouse-selection store, single-key `mouseterm-tut-v3` localStorage scheme, theme picker, and FakePtyAdapter extensions (`sendOutput`, `pumpActivity`, `setInputHandler`). Read this when touching: `website/src/pages/Playground.tsx`, `website/src/lib/tut-runner.ts`, `website/src/lib/tut-detector.ts`, `website/src/lib/tutorial-state.ts`, `website/src/lib/tut-items.ts`, `website/src/lib/tutorial-shell.ts`, `lib/src/components/ThemePicker.tsx`, `lib/src/lib/themes/`, `lib/src/lib/platform/fake-scenarios.ts` (tutorial scenarios), the `WallEvent` union, or the `onApiReady`/`onEvent`/`initialPaneIds` props on Wall. +- **`docs/specs/tutorial.md`** — Playground tutorial on the website: 3-pane layout, interactive `tut` TUI runner with three sections (keyboard navigation, alerts/TODOs, copy/paste), per-item detection wired to `WallEvent` / activity store / mouse-selection store, single-key `dormouse-tut-v3` localStorage scheme, theme picker, and FakePtyAdapter extensions (`sendOutput`, `pumpActivity`, `setInputHandler`). Read this when touching: `website/src/pages/Playground.tsx`, `website/src/lib/tut-runner.ts`, `website/src/lib/tut-detector.ts`, `website/src/lib/tutorial-state.ts`, `website/src/lib/tut-items.ts`, `website/src/lib/tutorial-shell.ts`, `lib/src/components/ThemePicker.tsx`, `lib/src/lib/themes/`, `lib/src/lib/platform/fake-scenarios.ts` (tutorial scenarios), the `WallEvent` union, or the `onApiReady`/`onEvent`/`initialPaneIds` props on Wall. - **`docs/specs/theme.md`** — Theme system: two-layer CSS variable strategy, theme data model, conversion pipeline, bundled themes, localStorage store, shared ThemePicker component, standalone AppBar picker, runtime OpenVSX installer. Read this when touching: `lib/src/lib/themes/`, `lib/src/components/ThemePicker.tsx`, `lib/src/theme.css`, `lib/scripts/bundle-themes.mjs`, `standalone/src/AppBar.tsx` (theme picker), `standalone/src/main.tsx` (theme restore), or `website/src/components/SiteHeader.tsx` (themeAware mode). - **`docs/specs/mouse-and-clipboard.md`** — Terminal-owned text selection, copy (Raw / Rewrapped), bracketed paste, smart URL/path extension, mouse-reporting override UI (icon + banner), and the state matrix for which layer owns mouse events. Read this when touching: `lib/src/lib/mouse-selection.ts`, `lib/src/lib/mouse-mode-observer.ts`, `lib/src/lib/clipboard.ts`, `lib/src/lib/rewrap.ts`, `lib/src/lib/selection-text.ts`, `lib/src/lib/smart-token.ts`, `lib/src/components/SelectionOverlay.tsx`, `lib/src/components/SelectionPopup.tsx`, the mouse icon / override banner / Cmd+C-V handling in `lib/src/components/Wall.tsx`, or the parser hooks + mouse listeners in `lib/src/lib/terminal-registry.ts`. diff --git a/DESIGN.md b/DESIGN.md index b22e51f9..d75d379c 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -1,5 +1,5 @@ --- -name: MouseTerm +name: Dormouse description: A mouse-friendly multitasking terminal that feels native inside VSCode. colors: app-bg: "var(--vscode-sideBar-background)" @@ -85,13 +85,13 @@ components: padding: "16px 24px" --- -# Design System: MouseTerm +# Design System: Dormouse ## 1. Overview **Creative North Star: "The Native Tenant"** -MouseTerm is a tenant in someone else's house. The house is VSCode. The user picked the furniture (their theme), the lighting (their mode), the typography (their editor font). MouseTerm moves in, multiplies what the user can do with their terminals, and leaves the decor alone. The interface should be indistinguishable from a built-in panel: not because it imitates VSCode, but because it inherits from VSCode. Every color, every font, every surface is a passthrough of the host's tokens. +Dormouse is a tenant in someone else's house. The house is VSCode. The user picked the furniture (their theme), the lighting (their mode), the typography (their editor font). Dormouse moves in, multiplies what the user can do with their terminals, and leaves the decor alone. The interface should be indistinguishable from a built-in panel: not because it imitates VSCode, but because it inherits from VSCode. Every color, every font, every surface is a passthrough of the host's tokens. The system is intentionally minimal and bg-only. Chrome recedes; terminals are the content. Hierarchy is conveyed through background shifts between `header-active-bg` and `header-inactive-bg`, not through borders, shadows, or accent stripes. Status is conveyed through shape and position (a bell icon, a door's alert state) and through the active terminal palette's own ANSI red/green/yellow, not through a separate design-system palette. @@ -107,7 +107,7 @@ The system explicitly rejects: rounded SaaS cards, gradient accents, hacker-aest ## 2. Colors -The palette has no fixed values. Every semantic token resolves to a `--vscode-*` variable at runtime. Inside VSCode, those variables are injected by the host. Outside VSCode (standalone, website playground), `applyTheme()` materializes the same variable shape on `document.body` from a bundled MouseTerm theme. +The palette has no fixed values. Every semantic token resolves to a `--vscode-*` variable at runtime. Inside VSCode, those variables are injected by the host. Outside VSCode (standalone, website playground), `applyTheme()` materializes the same variable shape on `document.body` from a bundled Dormouse theme. ### Primary This system has no "primary" accent in the brand sense. The closest analogue is the **focused-header pair**: @@ -152,7 +152,7 @@ This system has no "primary" accent in the brand sense. The closest analogue is **Body Font:** `var(--vscode-editor-font-family)`. **Label/Mono Font:** same as body. Sans and mono resolve to the same VSCode editor font. -**Character:** monospace, the user's own editor face. The system has no opinion about Cascadia vs. SF Mono vs. JetBrains Mono vs. Fira Code; whatever is set in the editor is what MouseTerm uses, including ligature settings. This is the typographic equivalent of the host-theme rule. +**Character:** monospace, the user's own editor face. The system has no opinion about Cascadia vs. SF Mono vs. JetBrains Mono vs. Fira Code; whatever is set in the editor is what Dormouse uses, including ligature settings. This is the typographic equivalent of the host-theme rule. ### Hierarchy - **Body** (weight 500, `text-sm` = 0.75rem / 12px, line-height 1rem): pane headers, doors, popup contents, button labels. The single most-used step. diff --git a/PRODUCT.md b/PRODUCT.md index ad907a8c..41a03e61 100644 --- a/PRODUCT.md +++ b/PRODUCT.md @@ -10,11 +10,11 @@ Developers ranging from terminal beginners (non-developers using Claude Code for ### Brand Personality **Focused. Approachable. Capable.** -MouseTerm should feel like focused efficiency that cares about beginners and onboarding, without sacrificing anything that even the most extreme power user might want eventually. The interface should communicate: "everything is under control" — no clutter, no distraction, just the information you need when you need it. +Dormouse should feel like focused efficiency that cares about beginners and onboarding, without sacrificing anything that even the most extreme power user might want eventually. The interface should communicate: "everything is under control" — no clutter, no distraction, just the information you need when you need it. ### Aesthetic Direction -**Primary constraint: Feel native inside VSCode.** The current Catppuccin Mocha design is throwaway — built to get things running. The first design priority is making MouseTerm feel completely native within VSCode, respecting whatever theme the user has chosen. This means: +**Primary constraint: Feel native inside VSCode.** The current Catppuccin Mocha design is throwaway — built to get things running. The first design priority is making Dormouse feel completely native within VSCode, respecting whatever theme the user has chosen. This means: - Use VSCode's CSS variables and theme tokens, not hardcoded colors - Match VSCode's spacing, typography, and interaction patterns - Light mode and dark mode support from the start (inherited from user's VSCode theme) @@ -23,7 +23,7 @@ MouseTerm should feel like focused efficiency that cares about beginners and onb **After VSCode-native is achieved**, figure out the standalone terminal's visual identity separately. **References:** -- VSCode itself — the gold standard for how MouseTerm should feel as an extension +- VSCode itself — the gold standard for how Dormouse should feel as an extension - The tool should feel like a natural part of the editor, not a foreign embed **Anti-references:** @@ -34,7 +34,7 @@ MouseTerm should feel like focused efficiency that cares about beginners and onb ### Design Principles -1. **Native first** — Inside VSCode, MouseTerm should be indistinguishable from a built-in feature. Use the host's theme tokens, spacing, and conventions. Never fight the environment. +1. **Native first** — Inside VSCode, Dormouse should be indistinguishable from a built-in feature. Use the host's theme tokens, spacing, and conventions. Never fight the environment. 2. **Information density without intimidation** — Power users want dense layouts with many terminals visible. Beginners need to not feel overwhelmed. Solve this with progressive disclosure: simple by default, powerful when you explore. diff --git a/docs/specs/OSC.md b/docs/specs/OSC.md index e30281a7..c85fa51b 100644 --- a/docs/specs/OSC.md +++ b/docs/specs/OSC.md @@ -1,10 +1,10 @@ # OSC Sequence Registry -> Single registry of OSC sequences MouseTerm parses. Behavioral details live in `docs/specs/alert.md` (notifications) and `docs/specs/terminal-state.md` (CWD, prompt/command, title fallback). This file also documents iTerm2 self-identification because the same identity is what causes most of these sequences to be emitted at us. +> Single registry of OSC sequences Dormouse parses. Behavioral details live in `docs/specs/alert.md` (notifications) and `docs/specs/terminal-state.md` (CWD, prompt/command, title fallback). This file also documents iTerm2 self-identification because the same identity is what causes most of these sequences to be emitted at us. ## Goal -MouseTerm parses a small set of OSC (Operating System Command) escape sequences from PTY output to drive alerts, terminal state, and titles. This document is the index — every supported OSC has one row in the table below pointing to the spec that defines its full behavior. +Dormouse parses a small set of OSC (Operating System Command) escape sequences from PTY output to drive alerts, terminal state, and titles. This document is the index — every supported OSC has one row in the table below pointing to the spec that defines its full behavior. ## Parsing location @@ -28,7 +28,7 @@ The parser also classifies each PTY data chunk for activity-monitor purposes: - A chunk that contains only notification/progress OSCs after parsing must not be fed to the activity monitor's `onData()` as generic meaningful output. - A chunk that contains visible output plus notification/progress OSCs still counts visible output as activity. -Unknown non-iTerm2 OSC families pass through to xterm.js unchanged so xterm.js can handle standard terminal behavior MouseTerm does not model. Security-sensitive or iTerm2-identity-triggered OSCs must not rely on xterm.js defaults: if they are not in [Supported OSCs](#supported-oscs), MouseTerm consumes and ignores them without visible terminal garbage, clipboard access, file access, focus changes, or other side effects. +Unknown non-iTerm2 OSC families pass through to xterm.js unchanged so xterm.js can handle standard terminal behavior Dormouse does not model. Security-sensitive or iTerm2-identity-triggered OSCs must not rely on xterm.js defaults: if they are not in [Supported OSCs](#supported-oscs), Dormouse consumes and ignores them without visible terminal garbage, clipboard access, file access, focus changes, or other side effects. ## Supported OSCs @@ -53,14 +53,14 @@ Some sequences are dual-purpose. The notification rows for `OSC 9 ; ST ## iTerm2 identity -MouseTerm reports an iTerm2-compatible identity so that tools (shells, build systems, agent clients) emit the iTerm2-style escape codes that this spec set supports. +Dormouse reports an iTerm2-compatible identity so that tools (shells, build systems, agent clients) emit the iTerm2-style escape codes that this spec set supports. Environment for spawned PTYs: | Variable | Value | |---|---| | `TERM_PROGRAM` | `iTerm.app` | -| `TERM_PROGRAM_VERSION` | MouseTerm's chosen iTerm2 compatibility version, not the package version | +| `TERM_PROGRAM_VERSION` | Dormouse's chosen iTerm2 compatibility version, not the package version | | `LC_TERMINAL` | `iTerm2` only if needed by real-world shell integrations | | `LC_TERMINAL_VERSION` | same compatibility version as `TERM_PROGRAM_VERSION` | @@ -70,16 +70,16 @@ Device/version query: - Use a single compatibility version across env and device responses. - Do not advertise feature-specific support until the relevant behavior exists. -Because this identity can cause tools to emit more iTerm2 escape codes than MouseTerm implements, **unsupported escape codes must fail inertly**: consume or ignore them without visible terminal garbage, privilege escalation, clipboard access, file access, or focus stealing. +Because this identity can cause tools to emit more iTerm2 escape codes than Dormouse implements, **unsupported escape codes must fail inertly**: consume or ignore them without visible terminal garbage, privilege escalation, clipboard access, file access, or focus stealing. ## Known-unimplemented iTerm2 and clipboard-capable sequences -MouseTerm intentionally does not implement the following sequences. They are mostly iTerm2-proprietary; `OSC 50` (font) and `OSC 52` (clipboard) are standard xterm extensions included here because the iTerm2 identity prompts tools to emit them and they have security implications. All of them must fail inertly per the rule above, which means they are consumed/ignored rather than forwarded to xterm.js. +Dormouse intentionally does not implement the following sequences. They are mostly iTerm2-proprietary; `OSC 50` (font) and `OSC 52` (clipboard) are standard xterm extensions included here because the iTerm2 identity prompts tools to emit them and they have security implications. All of them must fail inertly per the rule above, which means they are consumed/ignored rather than forwarded to xterm.js. | Sequence | Purpose | Reason for non-support | |---|---|---| -| `OSC 1337 ; SetMark` | Pin a navigable scrollback mark | No mark UI in MouseTerm. | -| `OSC 1337 ; CursorShape=...` | Cursor shape override | Cursor shape comes from MouseTerm settings, not the PTY. | +| `OSC 1337 ; SetMark` | Pin a navigable scrollback mark | No mark UI in Dormouse. | +| `OSC 1337 ; CursorShape=...` | Cursor shape override | Cursor shape comes from Dormouse settings, not the PTY. | | `OSC 1337 ; SetBadgeFormat=...` | Display a badge string in the terminal | No badge UI. | | `OSC 1337 ; ClearScrollback` | Clear scrollback buffer | xterm.js handles native clear-screen sequences. | | `OSC 1337 ; CopyToClipboard=...` / `EndCopy` | Programmatic clipboard write | Security: untrusted PTY output cannot write the user's clipboard. See `docs/specs/mouse-and-clipboard.md`. | @@ -89,7 +89,7 @@ MouseTerm intentionally does not implement the following sequences. They are mos | `OSC 50 ; ST` | Set font dynamically | Font is host-controlled. | | `OSC 52 ; ; ST` | Programmatic clipboard write | Security: same rationale as `CopyToClipboard`. | -This list is non-exhaustive. Any iTerm2-compatibility OSC family that MouseTerm can identify and that is not in the [Supported OSCs](#supported-oscs) table is ignored. +This list is non-exhaustive. Any iTerm2-compatibility OSC family that Dormouse can identify and that is not in the [Supported OSCs](#supported-oscs) table is ignored. ## References diff --git a/docs/specs/alert.md b/docs/specs/alert.md index 0c23b3d5..e7016243 100644 --- a/docs/specs/alert.md +++ b/docs/specs/alert.md @@ -1,900 +1,334 @@ # Alert Spec -## Goal +Alert state belongs to the **Session** Activity layer. It survives Pane <-> Door movement and is destroyed with the Session. -The alert system is a reminder for a **Session** that may finish work while the user is looking elsewhere. Alert state lives on the Session itself, not on the Pane or Door that currently displays it. +Dormouse can owe the user attention in three ways: -There are three independent ways a Session can become alert-worthy: +- **WATCHING**: the user enabled the timer-based output monitor, output became busy, then went quiet while the user was not attending the Session. +- **Terminal report**: the PTY emitted a supported notification or progress protocol (`BEL`, `OSC 9`, `OSC 9;4`, `OSC 99`, or `OSC 777`). +- **Command exit**: Dormouse saw a foreground command running while the user attended the Session, attention was lost while that same command was still running, and the command exited after at least `T_USER_ATTENTION`. -- **WATCHING**: the user explicitly enables MouseTerm's timer-based watcher, output motion becomes busy, then motion stops while the Session lacks attention. -- **Explicit protocol notification**: the PTY emits a supported terminal notification/progress report (`OSC 9`, `OSC 9;4`, `OSC 99`, `OSC 777`, or standalone `BEL`) while the Session lacks attention. -- **Command exit after attention loss**: MouseTerm observes a foreground command running while the Session has attention, attention later expires or is explicitly lost, and that same command exits after running for at least `T_USER_ATTENTION`. +Terminal-report and command-exit alerts do not require WATCHING to be enabled. All three paths share the same attention suppression rule: do not ring if the user is actively attending that Session at the completion moment. -Protocol notification/progress reports and command-exit alerts are not controlled by the WATCHING toggle. The OSC sequence registry and parsing-location rules live in `docs/specs/OSC.md`; command lifecycle state comes from `docs/specs/terminal-state.md`. +## Non-goals -This spec uses semantic state names that describe what the Session currently owes the user: +- No command/process heuristics. Dormouse does not guess that `vim`, `npm dev`, agents, or test runners deserve special alert behavior. +- No sound, native OS notifications, browser notifications, or separate progress-bar widget. +- No process-tree introspection for command-exit alerts; normalized terminal semantic events are the reliable input. +- No HTML, Markdown, ANSI styling, clickable actions, custom icons, or remote-controlled buttons in notification previews. +- No Door-specific alert menu that changes the Door actions defined in `docs/specs/layout.md`. -- `NOTHING_TO_SHOW` -- `MIGHT_BE_BUSY` -- `BUSY` -- `OSC_NOTIF_BUSY` -- `COMMAND_EXIT_ARMED` -- `MIGHT_NEED_ATTENTION` -- `ALERT_RINGING` +## Public State -This document is the source of truth for the naming and behavior of this state machine. +The public Activity state is: -## Non-goals +```ts +type WatchingSessionStatus = + | 'WATCHING_DISABLED' + | 'NOTHING_TO_SHOW' + | 'MIGHT_BE_BUSY' + | 'BUSY' + | 'MIGHT_NEED_ATTENTION' + | 'ALERT_RINGING'; -- No per-tool allow/deny heuristics. We do not try to guess whether `vim`, `npm dev`, `claude`, or any other command is "appropriate" for alerts. -- No sound, native OS notifications, or browser notifications in v1. "Alarm" means MouseTerm's existing `ALERT_RINGING` visual state. -- No standalone progress bar widget. `OSC 9;4` progress updates `protocolStatus` while active; completion/error creates TODO detail. It does not add a separate progress widget to the Pane header. -- No process-tree introspection for command-exit alerts in v1. Shell integration (`OSC 133` / `OSC 633`) is the reliable path. Heuristic user-input/prompt fallback may be used as a best-effort source, but deeper shell integration remains an open TODO. -- No full iTerm2/kitty/rxvt/WezTerm feature parity. Unsupported sequences are ignored unless another spec claims them. -- No HTML, Markdown, ANSI styling, shell command parsing, or clickable action buttons inside TODO notification previews. -- No Door-specific alert menu that overrides the existing click-to-reattach behavior from `docs/specs/layout.md`. - -## When alerts are useful - -Alerts are most useful for sessions such as: - -- long-running jobs that eventually finish, such as signing, notarization, deploys, or test runs -- slow human-in-the-loop sessions, such as AI chats where the user may switch to other work - -Alerts are usually not useful for sessions such as: - -- continuous background output, such as `npm dev` -- fast local interactive tools where the user is already present -- read-only streams that the user expects to keep changing forever - -This is guidance only. The system does not auto-enable or auto-disable alerts based on process name, shell command, exit code, or output patterns. - -## Data model - -Each Session owns: - -- `status: 'WATCHING_DISABLED' | 'NOTHING_TO_SHOW' | 'MIGHT_BE_BUSY' | 'BUSY' | 'OSC_NOTIF_BUSY' | 'COMMAND_EXIT_ARMED' | 'MIGHT_NEED_ATTENTION' | 'ALERT_RINGING'` - - This is the public projected alert and activity state for the Session. - - `WATCHING_DISABLED`: WATCHING is off and no stronger protocol or command-exit state is active. Default state. - - Stable states: `WATCHING_DISABLED`, `NOTHING_TO_SHOW`, `BUSY`, `OSC_NOTIF_BUSY`, `COMMAND_EXIT_ARMED`, `ALERT_RINGING`. - - Transitional states: `MIGHT_BE_BUSY`, `MIGHT_NEED_ATTENTION`. - - When the user enables WATCHING, `watchingStatus` transitions from `WATCHING_DISABLED` to `NOTHING_TO_SHOW` and timer-based activity tracking begins fresh from that moment. - - When the user disables WATCHING, timer-based activity tracking stops and `watchingStatus` returns to `WATCHING_DISABLED`. Public `status` may still be `OSC_NOTIF_BUSY`, `COMMAND_EXIT_ARMED`, or `ALERT_RINGING` if another track is active. -- `watchingStatus: 'WATCHING_DISABLED' | 'NOTHING_TO_SHOW' | 'MIGHT_BE_BUSY' | 'BUSY' | 'MIGHT_NEED_ATTENTION' | 'ALERT_RINGING'` - - Internal timer-based status owned by the existing activity monitor. - - It is driven only by meaningful output, silence timers, and attention. - - Spec prose should use WATCHING terminology for this track. -- `watchingEnabled: boolean` - - Public boolean exposed to UI and persistence so the WATCHING toggle remains accurate while `status` is projected to `OSC_NOTIF_BUSY`, `COMMAND_EXIT_ARMED`, or `ALERT_RINGING`. - - This is `true` exactly when the Session owns an active WATCHING monitor. -- `protocolStatus: 'IDLE' | 'OSC_NOTIF_BUSY' | 'ALERT_RINGING'` - - Internal terminal-report status owned by parsed terminal reports (see [Notification protocols](#notification-protocols)). - - It is driven only by terminal reports such as `OSC 9`, `OSC 9;4`, `OSC 99`, `OSC 777`, and standalone `BEL`. - - It does not use output/silence timers from the WATCHING activity monitor. - - It does use the shared attention model. A protocol completion/notification received while the user is actively attending that Session must not ring. - - `OSC_NOTIF_BUSY` means a terminal report says work is in progress, but there is not yet a notification owed to the user. - - `ALERT_RINGING` means a terminal report explicitly created a notification or completed/errored a reported progress cycle. -- `commandExitStatus: 'IDLE' | 'COMMAND_EXIT_ARMED' | 'ALERT_RINGING'` - - Internal command-exit status owned by terminal semantic command lifecycle events. - - It is driven by `commandStart` / `commandFinish` events from `OSC 133`, `OSC 633`, or equivalent semantic sources. - - `COMMAND_EXIT_ARMED` means MouseTerm saw a foreground command while the Session had attention, then the Session lost attention while that same command was still running. - - `ALERT_RINGING` means that same command exited after running for at least `T_USER_ATTENTION` and the Session still lacked attention. -- `commandExitWatch: CommandExitWatch | null` - - Latest foreground command eligible for command-exit alerting. - - Cleared when the command finishes, another command starts, the user returns before finish, or the Session is destroyed. -- `todo: boolean` - - Reminder state for the Session. Default `false`. - - `false`: no TODO. - - `true`: TODO is shown. It may be set explicitly by the user, or auto-created when a ringing alert is dismissed by attention or by the bell. - - Dismissing a ringing alert when `todo` is already `true` leaves it `true`. - - Legacy persisted TODO encodings migrate into this boolean shape: `-1` / `false` / unknown values become `false`; numeric soft buckets, `2`, `'soft'`, and `'hard'` become `true`. - -Each Session also owns: - -- `attentionDismissedRing: boolean` - - True when the user attended to a ringing Session (clicked into the Pane, typed in passthrough, etc.). Cleared when the bell is next clicked or the alert is toggled/disabled. Used by the bell button to show the context menu on the next click instead of immediately disabling. -- `notification: ActivityNotification | null` - - Latest explicit protocol notification detail, when a Session received a supported terminal notification sequence. - - This metadata is attached to TODO/alert state; it does not replace the boolean `todo` model or the visible TODO pill text. - - `OSC 9;4` progress is tracked through `protocolStatus` while active; completion/error promotes it into this notification field. - -`ActivityNotification` shape (intentionally small — these are the only fields rendered): +type SessionStatus = + | WatchingSessionStatus + | 'OSC_NOTIF_BUSY' + | 'COMMAND_EXIT_ARMED'; -```ts -type ActivityNotificationSource = - | 'OSC 9' - | 'OSC 9;4' - | 'OSC 99' - | 'OSC 777' - | 'BEL' - | 'COMMAND_EXIT'; +type TodoState = boolean; interface ActivityNotification { - source: ActivityNotificationSource; + source: 'OSC 9' | 'OSC 9;4' | 'OSC 99' | 'OSC 777' | 'BEL' | 'COMMAND_EXIT'; title: string | null; body: string | null; } -``` - -Per-source mapping rules (full protocol semantics in [Notification protocols](#notification-protocols)): - -- `OSC 9` stores `{ source: 'OSC 9', title: null, body: message }`. -- `OSC 777` stores `{ source: 'OSC 777', title, body }`. -- `OSC 99` stores `{ source: 'OSC 99', title, body }` after chunk assembly and sanitization. -- `OSC 9;4` stores nothing while progress is active. On completion/error it generates `{ source: 'OSC 9;4', title, body }`, where `title` is a short summary such as `Progress complete`, `Progress error`, or `Progress warning`, and `body` contains the percent when available. -- Standalone `BEL` stores `{ source: 'BEL', title: 'Terminal bell', body: null }`. -- Command exit stores `{ source: 'COMMAND_EXIT', title: 'Command finished', body }`, where `body` contains the display command and exit status when available. -Persistence rules: +interface AlertState { + status: SessionStatus; + watchingEnabled: boolean; + todo: TodoState; + notification: ActivityNotification | null; + attentionDismissedRing: boolean; +} +``` -- Persist the latest `ActivityNotification` with the Session's alert state. -- Persist only sanitized text and metadata, not raw escape sequences. -- On restore, persisted notification detail should restore TODO detail, but must not create a fresh ring or re-cock the bell by itself. +Internal state is deliberately split into independent tracks: -The workspace owns: +- `watchingStatus`: `WatchingSessionStatus`, or `WATCHING_DISABLED` when no `ActivityMonitor` exists. +- `protocolStatus`: `IDLE | OSC_NOTIF_BUSY | ALERT_RINGING`. +- `commandExitStatus`: `IDLE | COMMAND_EXIT_ARMED | ALERT_RINGING`. +- `progress`: active `OSC 9;4` progress, if any. +- `commandExitWatch`: the current foreground command eligible for command-exit alerting, if any. -- `attentionSessionId: string | null` - - Which Session currently has the user's attention. -- `attentionTimer: timeout handle | null` - - Auto-clears `attentionSessionId` after `T_USER_ATTENTION`. Reset on each new attention event. +Public `status` is a projection: -Important invariants: +1. `ALERT_RINGING` if `protocolStatus`, `commandExitStatus`, or `watchingStatus` is ringing, in that order. +2. `OSC_NOTIF_BUSY` if protocol progress is active. +3. `COMMAND_EXIT_ARMED` if command-exit alerting is armed. +4. Otherwise `watchingStatus`. -- Alert state is session-scoped and survives Pane <-> Door transitions. -- `watchingStatus` describes what the timer-based WATCHING track owes the user since the last explicit attention boundary. -- `protocolStatus` describes what terminal reports say independently of the WATCHING track. -- `commandExitStatus` describes whether a known foreground command has been armed for exit-based alerting. -- Public `status` is a projection of those tracks for existing UI. -- Destroying a Session clears `todo`, `notification`, `protocolStatus`, and `commandExitStatus` with it; the activity monitor is disposed. -- Re-rendering, theme changes, resize reflow, or remounting a Pane must not create a new alert by themselves. +Persist `status`, `watchingEnabled`, `todo`, and sanitized `notification`. Restore `todo` and `notification`, then restart WATCHING only if `watchingEnabled` is true. Restore must not recreate protocol progress, command-exit arms, or a fresh ring; replay filtering in `docs/specs/OSC.md` prevents old terminal output from firing notification side effects again. -## Attention model +Legacy TODO values migrate to boolean: `2`, numeric soft buckets `[0, 1]`, `'soft'`, and `'hard'` become `true`; `false`, `-1`, and unknown values become `false`. -We only ring when a Session produces a completion signal and the user is not actively attending to that Session. +## Attention -`attentionSessionId` is set only by explicit user actions that plausibly mean "I am looking at this Session now": +`attentionSessionId` is set only by explicit user actions that plausibly mean "I am looking at this Session": - clicking a Pane body or Pane header - entering passthrough on a Pane - typing into a Session in passthrough - clicking a Door or pressing `Enter` on a Door, because both reattach into passthrough -These do **not** count as attention: - -- a Session merely being visible -- a Session merely being selected in command mode -- hovering -- a Door existing in the baseboard -- reattaching a Door with `d`, because that restores the Pane but stays in command mode - -Attention is lost when: - -- the user has not explicitly interacted with that Session for `T_USER_ATTENTION` -- the app loses focus -- the Session is minimized into a Door while it had attention -- the Session is destroyed - -`T_USER_ATTENTION` is intentionally finite so a user can run a slow command, walk away, and still get an alert later even if that Pane remained selected. It also acts as the minimum command runtime for command-exit alerts. Start with 15s and tune with real usage. +These do not count as attention: mere visibility, command-mode selection, hover, a Door existing in the baseboard, or reattaching a Door with `d` into command mode. -Doors never directly hold attention. A Door can only regain attention by being restored into a Pane through an action that enters passthrough. +Attention is lost when the attention timer expires, the app loses focus, the attended Session is minimized or destroyed, or another Session becomes attended. `T_USER_ATTENTION` is 15 seconds by default and also acts as the minimum runtime for command-exit alerts. -## State model +## WATCHING Track -There are three independent state models: +WATCHING is the user-controlled output/silence monitor. It starts fresh when enabled and is disposed when disabled. Meaningful output excludes resize redraw noise during `T_RESIZE_DEBOUNCE`; theme changes, remounts, DOM reparenting, selection, and focus changes are not output. -- **WATCHING track**: the existing timer-based activity monitor. It watches meaningful output, silence, and user attention only after the user has enabled WATCHING. Its internal state is `watchingStatus`. -- **Terminal-report track**: parsed terminal notification/progress reports from the PTY. It relies entirely on terminal reports and never uses the output/silence timers. Its internal state is `protocolStatus`. -- **Command-exit track**: parsed terminal semantic command lifecycle events. It arms only after the user has seen a foreground command running and later loses attention before that same command exits. Its internal state is `commandExitStatus`. +| State | Meaning | +|---|---| +| `WATCHING_DISABLED` | No monitor exists. | +| `NOTHING_TO_SHOW` | Monitor is active, but no reminder is owed. | +| `MIGHT_BE_BUSY` | Output may be turning into ongoing work. Debounce state. | +| `BUSY` | Enough output has arrived to treat the Session as doing work. | +| `MIGHT_NEED_ATTENTION` | A busy Session went quiet. Debounce state. | +| `ALERT_RINGING` | WATCHING observed likely completion while the Session lacked attention. | -The public `status` is a projection used by existing UI: +Timers live in `cfg.alert`: -1. If `protocolStatus === 'ALERT_RINGING'`, public `status = ALERT_RINGING`. -2. Else if `commandExitStatus === 'ALERT_RINGING'`, public `status = ALERT_RINGING`. -3. Else if `watchingStatus === 'ALERT_RINGING'`, public `status = ALERT_RINGING`. -4. Else if `protocolStatus === 'OSC_NOTIF_BUSY'`, public `status = OSC_NOTIF_BUSY`. -5. Else if `commandExitStatus === 'COMMAND_EXIT_ARMED'`, public `status = COMMAND_EXIT_ARMED`. -6. Else public `status = watchingStatus`. +| Timer | Default | Purpose | +|---|---:|---| +| `busyCandidateGap` | 1500 ms | elapsed output window before busy is plausible | +| `busyConfirmGap` | 500 ms | confirmation window for `MIGHT_BE_BUSY` | +| `mightNeedAttention` | 2000 ms | silence after `BUSY` before possible completion | +| `needsAttentionConfirm` | 3000 ms | additional silence before ringing | +| `resizeDebounce` | 500 ms | ignore resize redraw output | +| `userAttention` | 15000 ms | attention idle expiry and command-exit minimum runtime | -This projection is deliberate. No single combined enum should attempt to encode every combination of WATCHING/protocol/command-exit state. The terminal-report and command-exit paths must be able to ring without enabling WATCHING. All three tracks rely on the shared user-attention model. +WATCHING transitions: -### WATCHING track +- First output in `NOTHING_TO_SHOW` starts candidate tracking but stays `NOTHING_TO_SHOW`. +- Continued output across `busyCandidateGap` enters `MIGHT_BE_BUSY`; more output confirms `BUSY`, while no confirmation returns to `NOTHING_TO_SHOW`. +- Output in `BUSY` restarts the silence timer. +- Silence moves `BUSY -> MIGHT_NEED_ATTENTION -> ALERT_RINGING`, unless the Session has attention at confirmation time; if attended, reset to `NOTHING_TO_SHOW`. +- Output in `MIGHT_NEED_ATTENTION` returns to `BUSY`. +- `ALERT_RINGING` latches. New output without attention does not clear it; new output with attention starts a new `MIGHT_BE_BUSY` cycle. +- Attending or dismissing a WATCHING ring resets the monitor to `NOTHING_TO_SHOW`. -The point of the state machine is not to model every output blip. It is to answer a narrow question: +Rings must be caused by a fresh transition into `ALERT_RINGING`, never by rerender, theme change, remount, minimize, or reattach. -- Does this Session currently have nothing worth surfacing? -- Does it appear to be busy with ongoing work? -- Has it likely finished and now needs attention? +## Protocol Track -The `MIGHT_*` states exist only to absorb uncertainty. They are debounce states, not user-facing end states. +Terminal notifications are explicit requests for attention and bypass the WATCHING toggle. Direct notifications ring immediately only when the Session lacks attention; if the user has attention, that notification is suppressed and unrelated protocol progress is left alone. -### Timing reference +`OSC 9;4` active progress sets public `status = OSC_NOTIF_BUSY`. It never rings because of silence. It rings only when a completion, clear, or error report arrives while the Session lacks attention. Completion/error while attended clears the protocol progress without TODO or ring. -| Timer | Value | Purpose | -|---|---|---| -| `T_BUSY_CANDIDATE_GAP` | 1.5 s | enough elapsed time to treat ongoing output as a possible busy transition | -| `T_BUSY_CONFIRM_GAP` | 500 ms | window in `MIGHT_BE_BUSY` before reverting to `NOTHING_TO_SHOW` if no further output | -| `T_MIGHT_NEED_ATTENTION` | 2 s | silence after `BUSY` before suspecting completion | -| `T_ALERT_RINGING_CONFIRM` | 3 s | additional silence before confirming `ALERT_RINGING` | -| `T_RESIZE_DEBOUNCE` | 500 ms | ignore resize redraw noise | -| `T_USER_ATTENTION` | 15 s | attention idle expiry | - -All values are configurable via `cfg.alert`. Total silence from last meaningful output to `ALERT_RINGING`: 5 s (`T_MIGHT_NEED_ATTENTION` + `T_ALERT_RINGING_CONFIRM`). - -### State semantics - -- `NOTHING_TO_SHOW` - - Default state. - - The Session does not currently owe the user a reminder. - - Immediate command echo or a single quick response is not enough to leave this state. - -- `MIGHT_BE_BUSY` - - Transitional state entered when output suggests the Session may be moving from a quick response into ongoing work. - - If that suspicion is not confirmed quickly, fall back to `NOTHING_TO_SHOW`. - -- `BUSY` - - Stable state. - - There is enough evidence that the Session is doing ongoing work and may later produce something worth surfacing. - -- `OSC_NOTIF_BUSY` - - Stable projected state from the terminal-report track. - - The terminal explicitly reported ongoing progress or a similar protocol-backed busy condition. - - It looks the same as `BUSY` in the Pane header and Door, but it does not participate in WATCHING timers. - - WATCHING silence does not move it to `MIGHT_NEED_ATTENTION`; only a terminal report can clear it or promote it to `ALERT_RINGING`. - -- `COMMAND_EXIT_ARMED` - - Stable projected state from the command-exit track. - - MouseTerm saw a foreground command running while the Session had attention, and attention later expired or was explicitly lost while that command was still running. - - It looks the same as `BUSY` in the Pane header and Door, but it does not participate in WATCHING timers. - - Only the same command finishing, the user returning, another command starting, or Session teardown can clear or promote it. - -- `MIGHT_NEED_ATTENTION` - - Transitional state entered when a `BUSY` Session goes quiet. - - This may be true completion, or only a pause in output. - -- `ALERT_RINGING` - - Stable state. - - The Session likely completed a meaningful unit of work and the alert is actively ringing. - -### Transition rules - -| Current | Event | Next | Notes | -|---|---|---|---| -| any | explicit attention boundary | `NOTHING_TO_SHOW` | Clicking into the Pane, typing in passthrough, or restoring a Door via click/`Enter` starts a new cycle. | -| `NOTHING_TO_SHOW` | first meaningful output after an attention boundary | `NOTHING_TO_SHOW` | A single output burst may be only immediate feedback. | -| `NOTHING_TO_SHOW` | another meaningful output arrives after `T_BUSY_CANDIDATE_GAP`, or multiple rapid outputs continue through that gap | `MIGHT_BE_BUSY` | The Session may be entering a longer-running phase. | -| `MIGHT_BE_BUSY` | further output confirms ongoing work within `T_BUSY_CONFIRM_GAP` | `BUSY` | Enough evidence to treat the Session as busy. | -| `MIGHT_BE_BUSY` | output stops before confirmation | `NOTHING_TO_SHOW` | False positive; it was just a quick response. | -| `BUSY` | more meaningful output | `BUSY` | Stay busy. | -| `BUSY` | no meaningful output for `T_MIGHT_NEED_ATTENTION` | `MIGHT_NEED_ATTENTION` | The Session may have finished, or may only be pausing. | -| `MIGHT_NEED_ATTENTION` | output resumes | `BUSY` | It was only a pause. | -| `MIGHT_NEED_ATTENTION` | silence continues for `T_ALERT_RINGING_CONFIRM` and the Session lacks attention | `ALERT_RINGING` | This is the alert-eligible completion transition. | -| `MIGHT_NEED_ATTENTION` | silence continues for `T_ALERT_RINGING_CONFIRM` but the Session has attention | `NOTHING_TO_SHOW` | The user already sees it; no reminder is owed. | -| `ALERT_RINGING` | explicit attention boundary | `NOTHING_TO_SHOW` | The user attended to the result. | -| `ALERT_RINGING` | new meaningful output and the Session has attention | `MIGHT_BE_BUSY` | A new work cycle may be starting. | -| `ALERT_RINGING` | new meaningful output but the Session lacks attention | `ALERT_RINGING` | Latch: new output does not silently clear the alert without user awareness. | - -These transition rules apply to the WATCHING track only. `OSC_NOTIF_BUSY` and `COMMAND_EXIT_ARMED` are not entered, exited, or promoted by these timers. - -### Terminal-report track - -| Current | Event | Next | Notes | -|---|---|---|---| -| `IDLE` | terminal report starts progress (`OSC 9;4` active state) | `OSC_NOTIF_BUSY` | Cock the bell without enabling WATCHING. | -| `OSC_NOTIF_BUSY` | terminal report updates progress | `OSC_NOTIF_BUSY` | Refresh internal progress state. Public UI remains visually identical to `BUSY`. | -| `OSC_NOTIF_BUSY` | terminal report completes progress and Session lacks attention | `ALERT_RINGING` | Create `notification`, set `todo = true`, and ring. | -| `OSC_NOTIF_BUSY` | terminal report completes progress and Session has attention | `IDLE` | User already sees it; do not ring or create TODO. | -| `OSC_NOTIF_BUSY` | terminal report errors progress and Session lacks attention | `ALERT_RINGING` | Create error `notification`, set `todo = true`, and ring. | -| `OSC_NOTIF_BUSY` | terminal report errors progress and Session has attention | `IDLE` | User already sees it; do not ring or create TODO. | -| `OSC_NOTIF_BUSY` | Session destroyed | `IDLE` | Session teardown clears protocol state. | -| `IDLE` | explicit progress completion report (`OSC 9;4;1;100`) and Session lacks attention | `ALERT_RINGING` | Create generated completion `notification`, set `todo = true`, and ring. | -| `IDLE` | explicit progress completion report (`OSC 9;4;1;100`) and Session has attention | `IDLE` | User already sees it; do not ring or create TODO. | -| `IDLE` | explicit progress error report (`OSC 9;4;2`) and Session lacks attention | `ALERT_RINGING` | Create generated error `notification`, set `todo = true`, and ring. | -| `IDLE` | explicit progress error report (`OSC 9;4;2`) and Session has attention | `IDLE` | User already sees it; do not ring or create TODO. | -| `ALERT_RINGING` | explicit attention boundary / dismiss / TODO clear | `IDLE` | Public status falls back to the command-exit/WATCHING projection after protocol ring clears. | -| any | direct notification (`OSC 9`, completed `OSC 99`, `OSC 777`, standalone `BEL`) and Session lacks attention | `ALERT_RINGING` | Create `notification`, set `todo = true`, and ring immediately. | -| any | direct notification (`OSC 9`, completed `OSC 99`, `OSC 777`, standalone `BEL`) and Session has attention | unchanged | User already sees it; suppress that notification only. Do not create TODO, and do not clear unrelated active progress. | - -`OSC_NOTIF_BUSY` never auto-rings because of silence. If a program starts progress and never sends completion/error, MouseTerm remains cocked until another terminal report completes/errors the progress cycle or the Session is destroyed. - -### Command-exit track - -The command-exit track is intentionally stricter than WATCHING. It exists for this case: "I was watching a command run, then I stopped paying attention, then that command exited." - -| Current | Event | Next | Notes | -|---|---|---|---| -| `IDLE` | command starts while Session has attention | `IDLE` | Store `commandExitWatch` for that command. Do not arm until attention is lost. | -| `IDLE` | attention becomes active while a command is already running | `IDLE` | Store or update `commandExitWatch.seenWithAttentionAt`. | -| `IDLE` | watched command is still running and attention expires or is explicitly lost | `COMMAND_EXIT_ARMED` | Store `attentionLostAt`. | -| `COMMAND_EXIT_ARMED` | same command finishes, runtime is at least `T_USER_ATTENTION`, and Session lacks attention | `ALERT_RINGING` | Create generated command-exit notification, set `todo = true`, and ring. | -| `COMMAND_EXIT_ARMED` | same command finishes too quickly | `IDLE` | Clear without ringing. | -| `COMMAND_EXIT_ARMED` | PTY exits before a command-finish semantic event, runtime is at least `T_USER_ATTENTION`, and Session lacks attention | `ALERT_RINGING` | Treat process exit as the fallback finish event for commands such as `exec ` or shells that exit before emitting a finish marker. | -| `IDLE` | PTY exits before a command-finish semantic event | `IDLE` | Clear any stored `commandExitWatch`; a dead process must not become armed later. | -| `COMMAND_EXIT_ARMED` | Session regains attention before finish | `IDLE` | Clear the arm; the user is watching again. | -| any | a different command starts | `IDLE` | Replace the watch with the new command if it is eligible. | -| `ALERT_RINGING` | explicit attention boundary / dismiss / TODO clear | `IDLE` | Public status falls back to the other tracks. | -| any | Session destroyed | `IDLE` | Session teardown clears command-exit state. | - -Race rule: command-exit alerting is eligible only if attention was lost before the `commandFinish` event for the same command. If command finish and attention loss are observed in the opposite order, do not ring. - -Precedence rule: if a direct protocol notification/progress completion and command finish happen in the same parse batch, protocol detail wins. The command-exit track should not overwrite a richer protocol-generated `ActivityNotification`. - -### Meaningful output - -`Meaningful output` means terminal output that is not suppressed as incidental UI churn. In particular: - -- output during `T_RESIZE_DEBOUNCE` does not count -- theme changes, remounts, or DOM reparenting do not count -- pure selection or focus changes do not count - -The implementation may later learn additional suppressions, but this spec only requires resize churn suppression today. - -## Notification protocols - -Protocol notifications and standalone terminal bells are explicit application requests for attention. They bypass the WATCHING toggle: a Session may ring even when WATCHING is disabled. They must not ring while the user is actively attending that Session. - -Active/in-progress progress sequences do not ring immediately. They "cock" the alarm bell — MouseTerm treats active progress as an explicit finite-work cycle and exposes `OSC_NOTIF_BUSY`. Explicit completion/error progress reports may ring immediately when the Session lacks attention. - -The OSC sequence registry, parser placement, and stripping behavior live in `docs/specs/OSC.md`. This section defines per-protocol semantics for the five supported notification sources. - -| Protocol | Shape | Fields | Notes | -|---|---|---|---| -| `BEL` | `BEL` outside an OSC sequence | none | Generic terminal-bell notification. | -| `OSC 9` | `OSC 9 ; [message] ST` | `message` | iTerm2's legacy notification form. No title/body split. | -| `OSC 9;4` | `OSC 9 ; 4 ; [state] ; [progress] ST` or `OSC 9 ; 4 ST` | progress state/progress | Progress only. Cocks the bell and may later ring on completion/error. | -| `OSC 99` | `OSC 99 ; [metadata] ; [payload] ST` | metadata keys plus payload | kitty's rich notification protocol. Chunked and extensible. | -| `OSC 777` | `OSC 777 ; notify ; [title] ; [body] ST` | `title`, `body` | rxvt/WezTerm notification form. Only `notify` is supported. | +Protocol rings set `todo = true`, store the latest sanitized `ActivityNotification`, and set `protocolStatus = ALERT_RINGING`. Clearing the protocol ring returns `protocolStatus` to `IDLE` and public status falls back to command-exit or WATCHING state. ### Standalone BEL -A `BEL` byte outside an OSC sequence creates one generated notification: - -- `source: 'BEL'` -- `title: 'Terminal bell'` -- `body: null` +A `BEL` byte outside an OSC sequence is stripped from visible output and creates: -Standalone `BEL` is for compatibility with tools that choose a plain terminal-bell notification channel. It strips the bell byte from visible terminal output and rings through the same protocol path as OSC notifications, subject to the shared user-attention check. +```ts +{ source: 'BEL', title: 'Terminal bell', body: null } +``` -If a parse batch contains both standalone `BEL` and a richer OSC notification/progress event, MouseTerm keeps the richer OSC event and drops the generic `BEL` notification detail so `iterm2_with_bell`-style tools cannot overwrite useful TODO preview text. +If a parse batch also contains a richer OSC notification/progress event, drop the generic `BEL` detail so it cannot overwrite useful preview text. Multiple standalone bells in one batch collapse to one notification. ### OSC 9 -`OSC 9 ; [message] ST` creates one notification: - -- `source: 'OSC 9'` -- `title: null` -- `body: [message]` +`OSC 9 ; ST` creates: -The message is plain text. There is no formal title, subtitle, urgency, app id, or notification id. - -OSC 9 also feeds the title-candidate channel for header/door label derivation; that side effect is specified in `docs/specs/terminal-state.md` and does not affect alert behavior. - -### OSC 9;4 progress - -If the first OSC 9 parameter is `4`, the sequence belongs to the progress protocol: +```ts +{ source: 'OSC 9', title: null, body: message } +``` -- `OSC 9 ; 4 ST` clears progress -- `OSC 9 ; 4 ; 0 ST` clears progress -- `OSC 9 ; 4 ; 1 ; [0-100] ST` sets normal progress -- `OSC 9 ; 4 ; 2 ; [0-100?] ST` sets error progress -- `OSC 9 ; 4 ; 3 ST` sets indeterminate progress -- `OSC 9 ; 4 ; 4 ; [0-100] ST` sets warning progress +Empty sanitized messages are ignored. OSC 9 also feeds title-candidate derivation in `docs/specs/terminal-state.md`; that does not change alert behavior. -The official fields are only: +### OSC 9;4 Progress -- `state` -- optional `progress` percent +`OSC 9;4` is progress only. It has no title, body, urgency, id, app name, or action fields. -There is no title, body, subtitle, notification id, application name, urgency, or message text in `OSC 9;4`. +| Sequence | Meaning | +|---|---| +| `OSC 9 ; 4 ST` or `OSC 9 ; 4 ; 0 ST` | clear progress | +| `OSC 9 ; 4 ; 1 ; <0-100> ST` | normal progress; `100` is completion | +| `OSC 9 ; 4 ; 2 ; <0-100?> ST` | error progress; percent optional | +| `OSC 9 ; 4 ; 3 ST` | indeterminate active progress | +| `OSC 9 ; 4 ; 4 ; <0-100> ST` | warning active progress | -MouseTerm behavior: +Rules: -- Non-clear states create or update an internal protocol progress cycle. -- Active progress cocks the bell by setting `protocolStatus = OSC_NOTIF_BUSY`. Public `status` projects this as `OSC_NOTIF_BUSY`, which looks the same as `BUSY` but is independent of the timer-based activity monitor. -- `state = 1` with `progress < 100` is normal active progress. Do not ring. -- `state = 1` with `progress = 100` is a completion report. Ring immediately as a completed progress cycle only if the Session lacks attention. -- `state = 2` is an error signal. Ring immediately and attach a generated progress notification to the TODO only if the Session lacks attention. -- `state = 3` is indeterminate active progress. Do not ring until cleared or replaced by an error/completion signal. -- `state = 4` is warning active progress. Do not ring immediately; remember the warning internally, and if the cycle later rings MouseTerm preserves that warning in the generated progress notification. -- `state = 0` or abbreviated `OSC 9 ; 4 ST` clears progress. If it clears an active protocol progress cycle, ring as completion. If there was no active protocol progress cycle, ignore it. -- Invalid states, missing required progress values for states `1` and `4`, and out-of-range progress values are ignored. Clamp only for display if an implementation has already accepted the sequence. +- Active normal, warning, or indeterminate progress sets `protocolStatus = OSC_NOTIF_BUSY` and does not create TODO. +- `state=1, progress=100` rings as completion if unattended. +- `state=2` rings as error if unattended. +- Clear rings as completion only if there was an active progress cycle; otherwise ignore it. +- Warning progress does not ring by itself, but completion of a warning cycle uses the generated title `Progress warning`. +- Invalid states, missing required percents for states `1` and `4`, and out-of-range percents are ignored. -Progress completion creates a generated notification, but does not invent copy beyond the normalized progress summary. The TODO preview should say things like `Progress complete`, `Progress error`, `Progress warning`, or `Progress 75%` rather than replacing the TODO pill text. +Generated notifications use source `OSC 9;4`, title `Progress complete`, `Progress warning`, or `Progress error`, and body `Progress %` when a percent is known. ### OSC 777 -`OSC 777 ; notify ; [title] ; [body] ST` creates one notification: +`OSC 777 ; notify ; ; <body> ST` creates: -- `source: 'OSC 777'` -- `title: [title]` -- `body: [body]` +```ts +{ source: 'OSC 777', title, body } +``` -Only the `notify` subcommand is supported. The format has no escaping for semicolons. For compatibility, parse the title as the field after `notify` and treat the rest of the sequence after the next semicolon as the body, preserving additional semicolons in the body. A title containing a semicolon cannot be represented portably. +Only `notify` is supported. The first field after `notify` is the title; everything after the next semicolon is body, preserving additional semicolons there. Unsupported subcommands and empty sanitized notifications are ignored. ### OSC 99 -`OSC 99 ; [metadata] ; [payload] ST` uses colon-delimited metadata where each key is a single ASCII letter. Unknown keys are ignored. Unknown payload types are ignored unless this spec adds them later. +`OSC 99 ; <metadata> ; <payload> ST` is kitty's notification protocol. Metadata keys are single ASCII letters separated by `:`; unknown keys are ignored. -Initial supported metadata keys: +Supported keys: -| Key | Meaning | Initial MouseTerm behavior | +| Key | Meaning | Dormouse behavior | |---|---|---| -| `i` | notification identifier | Used to assemble chunks and coalesce updates for the same notification. | -| `d` | done flag, `0` or `1`, default `1` | `d=0` stores a partial notification without ringing. `d=1` completes and rings. | -| `e` | payload encoding, `0` plain or `1` base64 | Decode RFC 4648 base64 when `e=1`; reject invalid base64. | -| `p` | payload type, default `title` | Support `title` and `body`; handle management/query payloads separately. | -| `f` | base64 application name | Decode only if needed for protocol validity; do not store or render in this phase. | -| `t` | base64 notification type | Ignore in this phase. | -| `u` | urgency, `0`, `1`, or `2` | Ignore in this phase; urgency does not change alert mechanics. | -| `o` | occasion, `always`, `unfocused`, `invisible` | Parse but ignore for MouseTerm ringing; explicit OSC notifications always ring. | -| `w` | auto-close milliseconds | Parse but ignore for TODO lifetime. TODO clears only by MouseTerm's normal TODO clearing rules. | - -Payload types: - -| `p` value | Behavior | -|---|---| -| `title` | Append payload to the pending notification title. | -| `body` | Append payload to the pending notification body. | -| `?` | Support query. Does not ring. | -| `close` | Close/update management. Does not ring. | -| `alive` | Liveness query. Does not ring. | -| `icon` | Ignore payload content in this phase. Does not ring by itself. | -| `buttons` | Ignore payload content in this phase. Does not ring by itself. | - -Official kitty OSC 99 does not define a `subtitle` payload. If real-world agent tools emit `p=subtitle`, ignore it unless a later spec chooses to render a third user-facing text field. - -For a completed OSC 99 notification: - -- If title and body are both empty after sanitization, ignore it. -- If there is a body but no title, the body is the primary preview line. -- If there is a title but no body, render title only. -- If the same `i` arrives again after completion, treat it as an update to the same notification detail and ring again. -- If `i` is omitted, each completed notification is unique. - -Support query: - -- `OSC 99 ; i=[id] : p=? ; ST` must be answered with MouseTerm's actual support. -- Initial minimal response advertises only `title` and `body`, for example: `OSC 99 ; i=[id] : p=? ; o=always:p=title,body ST`. -- Preserve a valid query id in the response metadata. If the id is missing or cannot be safely echoed in OSC 99 metadata, omit `i=[id]` and respond with `OSC 99 ; p=? ; o=always:p=title,body ST`. -- Do not advertise click reports, close reports, urgency, sounds, icons, buttons, or auto-expiry unless implemented end-to-end. - -## Notification text handling - -Terminal notifications are untrusted terminal output. Treat all text as plain text. - -Input normalization: - -- Decode UTF-8 strictly enough to avoid replacement-character floods. -- Strip C0/C1 control characters after protocol parsing. -- Collapse CR/LF/TAB and other controls to spaces. -- Trim leading/trailing whitespace. -- Do not interpret ANSI, OSC, HTML, Markdown, URLs, shell paths, or emoji shortcodes as markup. - -Protocol-defined limits: - -- OSC 9;4 progress carries only a numeric state and optional numeric percent. There is no user-facing text payload. -- OSC 99 defines a payload chunk limit of 2048 bytes before base64 or 4096 bytes after base64. It permits chunking title/body multiple times, while allowing terminals to impose sensible denial-of-service limits. -- OSC 9 and OSC 777 do not define formal text length limits in the referenced terminal docs. - -MouseTerm-imposed limits: - -- Store at most 256 Unicode grapheme clusters for `title`. -- Store at most 4096 grapheme clusters for `body`. -- Parser memory for incomplete OSC 99 chunks is capped per Session. Drop the oldest incomplete chunks when the cap is exceeded. -- Expire incomplete OSC 99 chunks after 60 seconds if no `d=1` completion arrives. - -Expected UI copy length: - -- Titles are expected to be one short line, usually under 80 characters. -- Bodies are expected to be a few short lines at most. In MouseTerm chrome, show a compact preview and make the full stored body available in a popover/dialog. - -## Notification security - -Any remote process can emit these sequences over SSH. The feature is useful because it works over SSH, but the UI must be robust against hostile text. - -Requirements: - -- Sanitize all text before storing or rendering. -- Cap stored text and incomplete parser state. -- Never execute commands, open URLs, copy to clipboard, read files, or focus outside MouseTerm from these sequences. -- Do not render custom icons or buttons in this phase. -- Do not let notification text alter accessible labels beyond plain-text names. -- Do not allow repeated notifications to allocate unbounded history. Store only the latest detail, not an infinite list. - -## Alert trigger - -WATCHING alert logic is driven by transitions in `watchingStatus`. Protocol alert logic is driven by transitions in `protocolStatus`. Command-exit alert logic is driven by transitions in `commandExitStatus`. The public `status` projection reflects whichever track currently has the strongest user-facing claim. - -### WATCHING ring starts when all of these are true +| `i` | notification id | assemble chunks for the same pending notification | +| `d` | done flag, default `1` | `d=0` stores partial data; `d=1` completes and may ring | +| `e` | encoding, `0` plain or `1` base64 | decode base64 or reject invalid payload | +| `p` | payload type, default `title` | support `title`, `body`, `?`, `close`, `alive`, `icon`, `buttons` | -- the Session has WATCHING enabled (i.e. `watchingStatus !== 'WATCHING_DISABLED'`) -- the Session's `watchingStatus` transitions from `MIGHT_NEED_ATTENTION` into `ALERT_RINGING` -- the Session does not currently have attention +`title` and `body` chunks append to the pending notification. Completion rings once if the sanitized title or body is nonempty. If `i` is omitted, only a complete single-sequence notification is meaningful. -### Protocol override +Management payloads do not ring: -Supported terminal notification reports (see [Notification protocols](#notification-protocols)) may create a protocol ring. Supported `OSC 9;4` progress sequences set `protocolStatus = OSC_NOTIF_BUSY` and may later promote to `protocolStatus = ALERT_RINGING`. Protocol rings: +- `p=?` sends a support response advertising `o=always:p=title,body`. +- `p=close`, `p=alive`, `p=icon`, and `p=buttons` are consumed or ignored without creating notification UI. -- force public `status = ALERT_RINGING` even when the Session's activity monitor is disabled -- obey attention suppression because the user may already be typing into or reading that Session -- set `todo = true` and attach sanitized notification detail -- do not enable or disable the activity monitor -- return to `WATCHING_DISABLED` after dismissal if no activity monitor was enabled before the protocol ring +Pending OSC 99 chunks expire after 60 seconds, and at most 64 pending ids are retained per parser. -Implementation surface inside `AlertManager`: +## Command-exit Track -- A protocol-ring flag or source field independent of `ActivityMonitor`. -- `OSC 9;4` progress is tracked internally in `AlertManager`, not in public `ActivityState`. -- `getState(id).status` returns `ALERT_RINGING` while the protocol ring is active. -- `getState(id).status` returns `OSC_NOTIF_BUSY` while internal protocol progress is active and no stronger state is present. -- Dismiss/attend clears the protocol ring; status falls back to the command-exit/WATCHING projection or `WATCHING_DISABLED` if no stronger state exists. -- Completing or erroring a protocol progress cycle creates an `ActivityNotification` and promotes it into a protocol ring only if the Session lacks attention. -- Methods such as `notifyFromProtocol(id, notification)` and `updateProtocolProgress(id, state, percent)` are exposed through `PlatformAdapter` / VS Code messages. +The command-exit track consumes normalized semantic command events from `docs/specs/terminal-state.md` (`OSC 133`, `OSC 633`, or equivalent). It must not parse raw OSC itself. -### Command-exit override +Rules: -Terminal semantic command lifecycle events may create a command-exit ring. Command-exit rings: +- A command start creates `commandExitWatch` for the current foreground command. If the Session has attention, mark the command as seen. +- If the user attends while a command is already running, mark that command as seen. +- If attention is later lost while that same seen command is still running, set `commandExitStatus = COMMAND_EXIT_ARMED`. +- If the same command finishes, or the PTY exits before a finish event, ring only when all are true: it was armed, the Session still lacks attention, and runtime is at least `T_USER_ATTENTION`. +- A command-exit ring sets `todo = true` and stores `{ source: 'COMMAND_EXIT', title: 'Command finished', body }`, where body is the summarized command plus exit code when known. +- Returning to the Session before finish disarms the watch. A quick finish, a different command start, or Session destruction clears it without ringing. +- Race rule: attention must be lost before the finish event is observed. +- Precedence rule: a protocol ring must keep its richer `ActivityNotification`; command-exit must not overwrite it. -- force public `status = ALERT_RINGING` even when WATCHING is disabled -- obey attention suppression because the user may already have returned to that Session -- set `todo = true` and attach generated command-exit detail unless a richer protocol notification is already ringing -- do not enable or disable WATCHING -- return to `WATCHING_DISABLED` after dismissal if no WATCHING monitor was enabled and no protocol progress is active +## Clearing And TODO -Implementation surface inside `AlertManager`: +`todo` is a boolean reminder. Protocol and command-exit rings create it immediately. WATCHING rings create it when the user attends, dismisses, or marks TODO, so a dismissed ring does not disappear without a trace. -- A `commandExitStatus` field independent of `ActivityMonitor` and `protocolStatus`. -- A `commandExitWatch` record for the current foreground command, storing command id, display command, source, `startedAt`, `seenWithAttentionAt`, and `attentionLostAt`. -- `getState(id).status` returns `COMMAND_EXIT_ARMED` while command-exit alerting is armed and no stronger state is present. -- `applyTerminalSemanticEvents(id, events)` consumes normalized command lifecycle events; it must not parse raw OSC directly. -- Dismiss/attend/TODO-clear clears the command-exit ring; status falls back to protocol or WATCHING projection. -- Command-exit rings require command runtime `>= T_USER_ATTENTION`. +Clearing behavior: -### Ringing does not start when any of these are true +- Attending a ringing Session clears active protocol/command rings, resets a WATCHING ring, sets `todo = true`, and sets `attentionDismissedRing = true`. +- Clicking the ringing bell or pressing `a` dismisses the ring, sets `todo = true`, and opens the alert/TODO dialog. +- Marking TODO clears any active ring and leaves WATCHING enabled for future cycles. +- Clearing TODO sets `todo = false`, clears `notification`, and clears active protocol/command rings. +- Typing passthrough `Enter` into the Session clears TODO. Command-mode `Enter` that only enters passthrough does not. +- Disabling WATCHING disposes only the activity monitor. It does not clear protocol progress, command-exit arms, TODO, or notification detail. +- Destroying the Session clears all alert, TODO, notification, attention, protocol, and command-exit state. -- the Session already has attention at the moment it would otherwise enter `ALERT_RINGING` -- the Session is merely re-rendered or reattached while already `ALERT_RINGING` -- the only recent output was resize noise already ignored by the completion detector -- for WATCHING rings only: WATCHING is disabled (`watchingStatus === 'WATCHING_DISABLED'`) +`attentionDismissedRing` exists so the next bell click after an attention-based dismissal opens the dialog instead of silently disabling WATCHING. -This "fresh transition into `ALERT_RINGING` only" rule is critical. It prevents duplicate alerts on remount, theme change, or Pane <-> Door movement. +## UI Contract -Resize/activity-monitor suppression rules apply only to WATCHING rings. Attention suppression applies to WATCHING, protocol, and command-exit rings. +### Pane Header -## Alert clearing rules +The header shows: -For WATCHING rings, the Session leaves `ALERT_RINGING` and returns to `NOTHING_TO_SHOW` when any of these happen: +- an alert bell in every width tier +- a fixed-text `TODO` pill when `todo === true`, except in the minimal tier +- a hover/focus notification preview when TODO has `notification` +- a dialog from right-click or some left-click actions, containing TODO and WATCHING switches plus notification detail -- the user attends to the Session (clicking into the Pane, typing in passthrough, restoring a Door via click/`Enter`) -- the user dismisses the alert (clicking the ringing bell, pressing `a`) -- the user marks the Session as TODO (`t` key or context menu) -- new output arrives while the Session has attention (starts a new `MIGHT_BE_BUSY` cycle; without attention the alert stays ringing — see latch in transition rules) +Bell visual state is a pure function of public `status`: -All attention-based dismissals (the first three above) set `todo = true` if it is not already set. This prevents phantom dismissals where the alert vanishes without a trace. Once the TODO is visible, the user can clear it explicitly from the pill/dialog or by typing `Enter` as passthrough input into that Session's shell (i.e., the keystroke is forwarded to the PTY). The command-mode `Enter` that *switches into* passthrough does not clear the TODO. Synthetic terminal reports (focus events, cursor-position responses) also do not count as user input for clearing. - -For protocol rings (see [Notification protocols](#notification-protocols)), clearing the protocol ring sets `protocolStatus = IDLE` and returns public `status` to the projected command-exit/WATCHING state. If no WATCHING monitor was enabled before the protocol ring and no command-exit state is active, the Session returns to `WATCHING_DISABLED`. - -For command-exit rings, clearing the command-exit ring sets `commandExitStatus = IDLE` and returns public `status` to the projected protocol/WATCHING state. If no WATCHING monitor was enabled and no protocol state is active, the Session returns to `WATCHING_DISABLED`. - -The WATCHING track leaves `ALERT_RINGING` and returns to `WATCHING_DISABLED` when: - -- the user disables WATCHING on that Session (disposes the activity monitor) - -Disabling WATCHING does not clear `protocolStatus` or `commandExitStatus`. If either stronger track is active, public `status` remains driven by that track. - -The Session's alert state is cleared entirely when: - -- the Session is destroyed - -If more output arrives later and the Session makes a fresh transition back into `ALERT_RINGING`, the alert rings again. - -Marking a Session as TODO resets a WATCHING alert to `NOTHING_TO_SHOW` and sets `todo = true`, but it does **not** disable future WATCHING. `todo` and the WATCHING toggle are separate concerns. Protocol and command-exit rings preserve the same TODO behavior; clearing TODO clears `notification` unless the user explicitly chooses a future "keep details" action. - -Disabling WATCHING disposes the activity monitor and returns `watchingStatus` to `WATCHING_DISABLED`. Public `status` returns to `WATCHING_DISABLED` only when `protocolStatus === 'IDLE'` and `commandExitStatus === 'IDLE'`. - -## UI - -### Pane header - -The Pane header exposes two independent concepts: - -- TODO pill -- alert button - -TODO pill: - -- toggled in command mode with `t` (`false` -> `true` -> `false`) -- shown when `todo === true` -- auto-created on alert dismiss or attention-based alert clearing -- typing `Enter` as passthrough input (forwarded to the Session's shell) clears the TODO; the command-mode `Enter` that switches *into* passthrough does not -- clicking the TODO pill clears it -- when TODO clears, the pill briefly morphs to a `✓` glyph in the success color (~500 ms) before unmounting — this marks the moment of completion so the pill never vanishes silently -- no empty placeholder when off -- the visible pill remains `TODO`. It does not resize to arbitrary notification text, and does not adopt protocol-supplied title/body strings. It may show a small dot treatment when notification detail is present, as long as the pill remains fixed-width enough for narrow headers. - -Alert button: - -- shown in all header tiers, including compact and minimal -- icon-only control with tooltip and accessible label -- visual states (pure function of `status`): - - `WATCHING_DISABLED`: `BellIcon` unfilled, muted - - `NOTHING_TO_SHOW`: `BellIcon` filled, muted, upright - - `MIGHT_BE_BUSY`: `BellIcon` filled, muted, tilted slightly (-22.5°) - - `BUSY`: `BellIcon` filled, muted, tilted 45° - - `OSC_NOTIF_BUSY`: same visual treatment as `BUSY` - - `COMMAND_EXIT_ARMED`: same visual treatment as `BUSY` - - `MIGHT_NEED_ATTENTION`: `BellIcon` filled, muted, tilted 60° - - `ALERT_RINGING`: `BellIcon` filled, warning color, rocking animation (±45° bell-ring keyframe); reduced-motion: static 45° tilt -- escalation is conveyed by increasing tilt angle, not by a separate badge element -- the tilt/animation must not change the button's layout size - -Interaction (`dismissOrToggleAlert` state machine): - -- left-click the bell while `WATCHING_DISABLED`: enables WATCHING (creates activity monitor) -- left-click the bell while `ALERT_RINGING`: dismisses the alert, creates a TODO if none exists, then opens the context menu anchored below the button -- left-click the bell after an attention-based dismissal (`attentionDismissedRing` is set): clears the flag and opens the context menu. This lets the user access TODO/disable options after attending to a ringing Session without requiring a right-click. -- left-click the bell while `OSC_NOTIF_BUSY`: does not clear protocol progress. If WATCHING is enabled, disables only WATCHING; if WATCHING is disabled, opens the context menu. -- left-click the bell while `COMMAND_EXIT_ARMED`: does not clear the command-exit arm. If WATCHING is enabled, disables only WATCHING; if WATCHING is disabled, opens the context menu. -- left-click the bell in any other WATCHING-enabled state: disables WATCHING (destroys activity monitor) -- pressing `a` on a selected Pane in command mode: same as left-click -- right-click the bell (any state): opens a context menu with: - - a TODO on/off switch with `[t]` shortcut hint - - a WATCHING on/off switch with `[a]` shortcut hint - - brief description of TODO clearing behavior -- tooltip includes "Right-click for options" hint - -The alert control has higher layout priority than split or zoom controls. Long titles must truncate before the bell disappears. - -### Notification preview and detail - -Protocol notification detail appears in a preview surface anchored below the TODO pill or alert bell: - -- Shown on TODO hover/focus. -- Shown when the selected Pane has a TODO with notification detail and there is enough space. -- Shown above a Door on hover/focus without changing Door click behavior. -- Click/`Enter` on a Door remains reattach-and-attend; no Door-only menus. - -Preview content: - -- Primary line: `title` if present, otherwise the first body excerpt. -- Body: clamp to 3 lines in the hover preview. -- For generated `OSC 9;4` notifications, title/body already contain the progress summary; no separate progress widget is rendered. -- Footer metadata: protocol source (`OSC 9`, `OSC 9;4`, `OSC 99`, `OSC 777`, `BEL`). - -A full detail dialog/popover may be opened from the preview or the existing alert context menu: - -- Text wraps and can scroll. -- No raw escape sequence is shown by default. -- Focus traps and `Escape` behavior follow [Accessibility and motion](#accessibility-and-motion). - -Recommended decision: do not replace TODO text with notification text. The header and Door need fixed, scannable indicators across many Sessions. Replacing `TODO` with unbounded remote-controlled text creates overflow, localization, spoofing, and attention-noise problems. A hover/selected expansion gives the notification context without destabilizing the layout. +| Status | Visual | +|---|---| +| `WATCHING_DISABLED` | outline bell, muted | +| `NOTHING_TO_SHOW` | filled bell, muted, upright | +| `MIGHT_BE_BUSY` | filled bell, muted, -22.5 degree tilt | +| `BUSY` | filled bell, muted, 45 degree tilt | +| `OSC_NOTIF_BUSY` | same as `BUSY` | +| `COMMAND_EXIT_ARMED` | same as `BUSY` | +| `MIGHT_NEED_ATTENTION` | filled bell, muted, 60 degree tilt | +| `ALERT_RINGING` | filled bell, warning color, rocking animation; reduced motion uses static 45 degree tilt | + +Tilt and animation must not change layout size. Long titles truncate before alert/TODO controls disappear. + +Bell interactions: + +- Left-click `WATCHING_DISABLED`: enable WATCHING. +- Left-click `ALERT_RINGING`: dismiss, create TODO if needed, open dialog. +- Left-click after `attentionDismissedRing`: clear the flag and open dialog. +- Left-click `OSC_NOTIF_BUSY` or `COMMAND_EXIT_ARMED`: if WATCHING is enabled, disable only WATCHING; otherwise open dialog. Do not clear protocol progress or command-exit arm. +- Left-click any other WATCHING-enabled state: disable WATCHING. +- Pressing `a` on the selected Pane in command mode uses the same action. +- Right-click always opens the dialog. +- Pressing `t` toggles TODO. + +The TODO pill always displays `TODO`; remote notification text belongs in preview/detail surfaces, not inside the pill. Clicking the pill clears TODO. On clear, the pill briefly shows the success flourish before unmounting. ### Door -A Door is display-only for alert state in v1. It must not replace the existing Door primary actions defined in `docs/specs/layout.md`. - -Door indicators: - -- show bell indicator only when `status !== 'WATCHING_DISABLED'` -- show TODO pill when `todo === true` -- if `status === 'ALERT_RINGING'`, the Door bell icon uses warning color and the same rocking animation as the Pane header -- the Door bell icon shows the same tilt angles as the Pane header for escalation states -- `OSC_NOTIF_BUSY` uses the same Door bell treatment as `BUSY` -- `COMMAND_EXIT_ARMED` uses the same Door bell treatment as `BUSY` - -Door interaction: - -- click or `Enter` keeps its existing meaning: reattach and enter passthrough -- `d` keeps its existing meaning: reattach and stay in command mode -- alert-specific actions are manipulated after restore, from the Pane header UI - -Consequences: - -- clicking or `Enter` on a ringing Door counts as attention and clears the ring -- `d` on a ringing Door does not count as attention, so the ring remains until the user explicitly attends, dismisses, or disables - -## Hardening requirements - -### Text overflow and narrow layouts - -- Session titles may contain long text, emoji, CJK, RTL text, combining marks, and shell prompts with paths. -- Pane titles and Door titles must use `min-width: 0` plus truncation so indicators do not overflow their containers. -- Bell and TODO indicators must be fixed-width, non-shrinking affordances. -- The ringing treatment must not change layout size. No border-width jumps, no icon-size jumps. -- If header space becomes extremely tight, the TODO pill may collapse before the alert control does. - -### Accessibility and motion - -- Ringing must not rely on color alone. Use icon state plus outline, fill, or pulse. -- Respect `prefers-reduced-motion`. In reduced-motion mode, replace the rocking animation with a steady 45° tilt. All tilt states are static transforms and work unchanged regardless of motion preference. -- Bell button must expose accurate `aria-label` text: - - "Enable alert" - - "Disable alert" - - "Alert ringing" -- TODO pill and bell actions must remain keyboard reachable. -- Any ringing modal or popover must trap focus, support `Escape`, and restore focus to the bell button when closed. - -### Session and lifecycle edge cases - -- Multiple Sessions may ring at once. Alert state is independent per Session. -- Minimizing or reattaching a ringing Session preserves the ring because the ring belongs to the Session. -- A Session that exits while ringing continues to ring until attended, dismissed, disabled, or destroyed by the user. -- Killing the Session clears all alert and TODO state because the Session no longer exists. -- If output resumes while a Session is ringing and the Session has attention, the ring clears and the Session returns to the normal state-machine flow. If the Session lacks attention, the ring persists (latch behavior prevents silent dismissal). -- App blur clears attention but does not dismiss existing rings. - -### Internationalization - -- Icon-only header controls avoid fixed-width translated labels. -- Tooltips, menus, and modal actions must wrap cleanly for longer translations. -- Use logical CSS properties where layout direction matters so RTL remains correct. -- The literal TODO pill may remain `TODO` in v1, but the layout must tolerate a longer localized label later. - -## Scenarios - -### Slow response, same pane, user walks away - -- User enables alert on a Pane. -- User runs a slow command. -- The Session progresses through `MIGHT_BE_BUSY` and `BUSY`. -- The Session later goes quiet, then transitions through `MIGHT_NEED_ATTENTION` into `ALERT_RINGING`. -- If `T_USER_ATTENTION` has expired, the Pane rings even if it remained selected. - -### Slow response, user switched elsewhere - -- User enables alert on Session A. -- Session A becomes `MIGHT_BE_BUSY`, then `BUSY`. -- User works in Session B or another app. -- Session A later goes quiet long enough to transition into `ALERT_RINGING`. -- Session A rings because it does not have attention. +A Door is display-only for alert state: -### Door rings, user wants to inspect immediately +- show the bell only when `status !== 'WATCHING_DISABLED'` +- show the TODO pill when `todo === true` +- use the same bell tilt/animation mapping as the Pane header +- do not expose a Door-specific alert menu -- User minimizes a WATCHING-enabled Session into a Door. -- The Session later transitions into `ALERT_RINGING`. -- The Door rings. -- User clicks the Door. -- The Session reattaches into passthrough and the ring clears. +Click or `Enter` on a Door reattaches into passthrough, counts as attention, and clears a ring. `d` reattaches in command mode, does not count as attention, and leaves the ring intact. -### Door rings, user wants to keep command-mode control +## Text And Security -- User minimizes a WATCHING-enabled Session into a Door. -- The Door starts ringing. -- User presses `d` on the Door in command mode. -- The Pane is restored, but the ring remains because the user has not yet explicitly attended to the Session. +Notification text is untrusted terminal output. -### User dismisses, then new output arrives +Sanitization and limits: -- A Session rings. -- User clicks into the pane to read the output. -- The alert clears, and a TODO appears. -- User presses `Enter` into the Session → the `TODO` pill morphs to a `✓` and clears (they engaged). -- The Session later emits new output, progresses through `BUSY`, and eventually reaches `ALERT_RINGING` again. +- Treat all text as plain text. +- Strip C0/C1 controls after protocol parsing, collapse whitespace controls to spaces, and trim. +- Do not interpret ANSI, OSC, HTML, Markdown, URLs, paths, or emoji shortcodes as markup. +- Store at most 256 Unicode code points for title and 4096 for body. +- Store only the latest `ActivityNotification`, not unbounded history. +- Cap and expire incomplete OSC 99 parser state as described above. -### User dismisses but doesn't engage +Security requirements: -- A Session rings. -- User clicks into the pane briefly, then switches to another session. -- The alert clears, and a TODO appears. -- User never presses `Enter` into the terminal → TODO persists. -- User later notices the TODO pill and clicks it to clear it. +- Never execute commands, open URLs, copy to clipboard, read files, focus outside Dormouse, or render protocol-supplied icons/buttons/actions. +- Notification text may appear only as plain text in visible UI and accessible labels. +- Layout must tolerate long text, CJK, RTL, combining marks, and emoji without pushing fixed controls out of bounds. -### OSC 9 rings with WATCHING disabled +## Hardening -- Session starts with `status = WATCHING_DISABLED`, `todo = false`. -- PTY emits `OSC 9 ; Build finished ST`. -- MouseTerm stores body `Build finished`, sets `todo = true`, and reports `ALERT_RINGING`. -- User clicks into the Pane. -- Ring clears. Because WATCHING was disabled, status returns to `WATCHING_DISABLED`; TODO remains until explicitly cleared or passthrough `Enter` is sent. +- Multiple Sessions can ring independently. +- Minimize, reattach, rerender, resize, and theme changes must preserve existing alert state without creating new rings. +- An exited Session may keep ringing until attended, dismissed, disabled, or destroyed. +- Ringing must not rely on color alone, and `prefers-reduced-motion` must be respected. +- Bell, TODO, preview, and dialog controls must remain keyboard reachable; dialogs trap focus and support `Escape`. +- Tooltips, dialog copy, and future localized TODO labels must wrap in narrow layouts. -### OSC 777 preserves title and body +## Verification Checklist -- PTY emits `OSC 777 ; notify ; Tests ; 341 passed ST`. -- Preview primary line is `Tests`. -- Preview body is `341 passed`. -- The TODO pill remains `TODO`. - -### OSC 99 chunked title/body - -- PTY emits `OSC 99 ; i=build-1:d=0 ; Build complete ST`. -- No ring yet. -- PTY emits `OSC 99 ; i=build-1:p=body:d=1 ; All tests passed ST`. -- MouseTerm combines title and body, then rings once. - -### OSC 9 progress cocks the bell - -- PTY emits `OSC 9 ; 4 ; 1 ; 50 ST`. -- MouseTerm stores progress `normal, 50%`. -- Public `status` becomes `OSC_NOTIF_BUSY`; the bell looks like `BUSY` without creating a TODO. -- PTY emits `OSC 9 ; 4 ; 0 ST` while the Session lacks attention. -- MouseTerm rings, sets `todo = true`, and the TODO preview says progress completed. - -### OSC 9 progress error rings immediately - -- PTY emits `OSC 9 ; 4 ; 2 ; 75 ST` while the Session lacks attention. -- MouseTerm stores progress `error, 75%`. -- MouseTerm rings immediately and attaches error progress detail to the TODO. - -### OSC notification while typing does not ring - -- User is typing into a Session in passthrough mode, so the Session has attention. -- PTY emits `OSC 9 ; Build finished ST`. -- MouseTerm does not ring and does not create a TODO because the user is already attending that Session. - -### Command exits after attention expires - -- User is typing into a Session in passthrough mode, so the Session has attention. -- PTY emits `OSC 633 ; E ; pnpm\x20build ST` and `OSC 633 ; C ST`; MouseTerm stores the foreground command as seen with attention. -- User stops interacting with that Session for at least `T_USER_ATTENTION`; MouseTerm clears attention and sets public `status = COMMAND_EXIT_ARMED`. -- The same command later emits `OSC 633 ; D ; 0 ST`. -- MouseTerm rings, sets `todo = true`, and stores a generated `COMMAND_EXIT` notification. - -### Quick command exit does not ring - -- User starts a command with attention and then immediately switches away. -- The command finishes before `T_USER_ATTENTION` elapsed since command start. -- MouseTerm clears the command-exit watch without ringing. - -### Returning before command exit disarms - -- User starts a command with attention, then attention expires and public `status = COMMAND_EXIT_ARMED`. -- User clicks back into the Session before the command finishes. -- MouseTerm clears the command-exit arm. If the command later finishes while the Session still has attention, it does not ring. - -### Restore does not replay old notifications - -- A Session receives an OSC notification and saves state with TODO detail. -- The app reloads and replays buffered output containing the original OSC. -- The TODO detail is restored from persisted state, but no fresh ring is emitted from replay. - -## Verification checklist - -WATCHING track: - -- Alert only rings on a fresh transition into `ALERT_RINGING` -- Single quick responses stay in `NOTHING_TO_SHOW` -- short pauses in a `BUSY` session only reach `MIGHT_NEED_ATTENTION`, not `ALERT_RINGING` -- Resize noise cannot cause a ring -- Minimize/reattach preserves alert state (`status` and `todo`) -- `d` restore from a Door does not silently clear a ring -- click/`Enter` restore from a Door does clear a ring -- very long titles do not push bell or TODO indicators out of bounds -- ringing is still understandable with reduced motion enabled -- multiple simultaneous ringing Sessions remain independently dismissible - -Notification protocols: - -- `OSC 9;message` rings and stores `message`. -- `OSC 9;4;1;50` sets `OSC_NOTIF_BUSY` and stores `normal, 50%` internally. -- `OSC 9;4;3` sets `OSC_NOTIF_BUSY` and stores indeterminate progress internally. -- `OSC 9;4;4;25` sets `OSC_NOTIF_BUSY` and stores warning progress internally. -- `OSC 9;4;2` rings immediately with indeterminate error detail. -- `OSC 9;4;0` rings as completion only if there was an active progress cycle. -- `OSC 9;4;1;100` rings immediately as an explicit completion report. -- Standalone `BEL` rings and stores generated terminal-bell detail. -- `OSC 777;notify;title;body` rings and stores title/body. -- Unsupported `OSC 777` subcommands are ignored. -- OSC 99 `d=0` chunks do not ring before completion. -- OSC 99 `d=1` completion rings once with combined title/body. -- OSC 99 `p=?` is answered and does not ring; `p=close`, `p=alive`, `p=icon`, and `p=buttons` do not ring by themselves. -- Extra standalone `BEL` in the same parse batch as a richer OSC event does not replace the richer notification detail. -- Protocol notifications ring with WATCHING disabled. -- Protocol notifications do not ring when the Session has attention. -- Dismissal returns a WATCHING-disabled Session to `WATCHING_DISABLED`. -- Dismissal returns a WATCHING-enabled Session to its monitor-backed state. -- TODO pill text remains stable under very long notification text. -- Hover/focus preview wraps long text and does not overflow narrow headers or Doors. -- Replay/restore does not re-fire notification side effects. - -Command-exit track: - -- Command start while attended stores a command-exit watch without ringing. -- Attention expiry while the same command is running sets `COMMAND_EXIT_ARMED`. -- Explicit attention loss while the same command is running sets `COMMAND_EXIT_ARMED`. -- Returning to the Session before finish clears `COMMAND_EXIT_ARMED`. -- The same command finishing after runtime `>= T_USER_ATTENTION` rings only if the Session lacks attention. -- The same command finishing before runtime `T_USER_ATTENTION` does not ring. -- A different command start replaces the prior watch. -- A protocol notification in the same parse batch as command finish wins over generated command-exit detail. +- WATCHING rings only on a fresh unattended transition into `ALERT_RINGING`. +- Quick output stays in `NOTHING_TO_SHOW`; pauses in busy output debounce through `MIGHT_NEED_ATTENTION`. +- Resize noise cannot cause a WATCHING ring. +- Alert/TODO state survives Pane <-> Door transitions. +- Door click/`Enter` clears a ring; Door `d` does not. +- Protocol notifications ring with WATCHING disabled, but not while the Session has attention. +- `OSC 9;4` active progress shows `OSC_NOTIF_BUSY`; completion, error, and active-progress clear ring only when unattended. +- Standalone `BEL` does not replace richer OSC detail in the same parse batch. +- OSC 99 chunking, base64, support query, and management payloads behave as specified. +- Command-exit arms only after a seen command loses attention and rings only on the same command after the minimum runtime. +- Protocol detail wins over generated command-exit detail. +- Dismiss/attend creates TODO; passthrough `Enter` clears TODO. +- Restore/replay does not refire old notification side effects. +- Long titles and notification text do not overflow fixed header or Door controls. ## References diff --git a/docs/specs/auto-update.md b/docs/specs/auto-update.md index bb347a58..ce095fa1 100644 --- a/docs/specs/auto-update.md +++ b/docs/specs/auto-update.md @@ -48,7 +48,7 @@ Update status appears as a text notice on the right side of the Baseboard (the a | `post-update-success` | "Updated to v0.5.0 — from v0.4.0" | "Changelog" | 10 seconds | | `post-update-failure` | "Update failed" | "Click here to debug" | No | -The "Install when I quit" action is the user's approval to download the update now and install it when they quit. The inline "Changelog" action calls Tauri's `getVersion()` and opens `https://mouseterm.com/changelog/after/<current-version>`. +The "Install when I quit" action is the user's approval to download the update now and install it when they quit. The inline "Changelog" action calls Tauri's `getVersion()` and opens `https://dormouse.sh/changelog/after/<current-version>`. When a notice has follow-up actions, it uses ` · ` as the separator between the message and action labels. All states are dismissible via [×]. Dismissing an unapproved `available` notice means no update is downloaded or installed in that session. Dismissing a `downloading` or `downloaded` notice hides it for the session only — it does not cancel an already-approved download/install. @@ -72,7 +72,7 @@ Windows uses `"installMode": "passive"` (configured in `tauri.conf.json` under ` ## localStorage -Single key: `mouseterm:update-result` +Single key: `dormouse:update-result` | Scenario | Value written | When cleared | |----------|--------------|--------------| @@ -99,7 +99,7 @@ In `standalone/src-tauri/tauri.conf.json`: "plugins": { "updater": { "pubkey": "<ed25519 public key>", - "endpoints": ["https://mouseterm.com/standalone-latest.json"], + "endpoints": ["https://dormouse.sh/standalone-latest.json"], "windows": { "installMode": "passive" } } } @@ -117,7 +117,7 @@ The Rust side registers the plugin with `tauri_plugin_updater::Builder::new().bu ## Design decisions -**Why install on quit after approval, not immediately?** MouseTerm is a terminal app with running processes. A mid-session relaunch would kill all sessions. By installing at quit time, the user has already decided to close their terminals. +**Why install on quit after approval, not immediately?** Dormouse is a terminal app with running processes. A mid-session relaunch would kill all sessions. By installing at quit time, the user has already decided to close their terminals. **Why no silent download?** Update bundles can be large, can fail for environment-specific reasons, and may surprise users who did not opt into changing the app. The launch probe is silent, but download/install only begins after explicit approval. diff --git a/docs/specs/deploy.md b/docs/specs/deploy.md index cce12a95..73bed060 100644 --- a/docs/specs/deploy.md +++ b/docs/specs/deploy.md @@ -21,7 +21,7 @@ Human-driven steps, in order: 4. **Push** — `git push && git push origin vX.Y.Z`. This triggers CI (Stage 1). 5. **Set environment variables** — copy the relevant secrets into the terminal from your password manager (see [Environment / secrets](#environment--secrets) for the list). 6. **Run local signing** — plug in the PIV USB key, then `./scripts/sign-and-deploy.sh all X.Y.Z`. The script waits for CI, downloads unsigned artifacts, signs macOS + Windows, generates the Tauri update manifest into `website/public/standalone-latest.json`, and creates the GitHub Release. Run `./scripts/sign-and-deploy.sh --help` for resume-after-failure subcommands. -7. **Deploy website** — commit the updated `website/public/standalone-latest.json` and deploy mouseterm.com so the updater endpoint is live. +7. **Deploy website** — commit the updated `website/public/standalone-latest.json` and deploy dormouse.sh so the updater endpoint is live. 8. **Verify the release** - Check GitHub Release assets are correct - On a Mac: extract the `.tar.gz`, open the `.app`, confirm no Gatekeeper warnings @@ -88,7 +88,7 @@ Runs on `ubuntu-latest`: 1. Checkout, setup Node 22, pnpm 10 2. `pnpm install --frozen-lockfile` at the repo root 3. `pnpm --filter mouseterm-lib test` -4. `pnpm --filter mouseterm build:frontend && pnpm --filter mouseterm build` +4. `pnpm --filter dormouse build:frontend && pnpm --filter dormouse build` 5. `npx vsce package --no-dependencies` 6. Upload `.vsix` as artifact @@ -136,29 +136,29 @@ codesign/jsign the executable ### Packaged app logging -Windows release builds use the GUI subsystem, so launching `mouseterm.exe` from a terminal returns immediately and does not stream stdout/stderr. The Tauri backend writes sidecar diagnostics to `%LOCALAPPDATA%\MouseTerm\mouseterm.log` on Windows, or to `$TMPDIR/mouseterm.log` on other platforms. Set `MOUSETERM_LOG_FILE` to override the path. +Windows release builds use the GUI subsystem, so launching `dormouse.exe` from a terminal returns immediately and does not stream stdout/stderr. The Tauri backend writes sidecar diagnostics to `%LOCALAPPDATA%\Dormouse\dormouse.log` on Windows, or to `$TMPDIR/dormouse.log` on other platforms. Set `DORMOUSE_LOG_FILE` to override the path. ## Artifact filenames -All release assets use **stable filenames** (no version in the name). This allows hotlinking directly from mouseterm.com via GitHub's `/latest/download/` redirect, which always resolves to the most recent release. +All release assets use **stable filenames** (no version in the name). This allows hotlinking directly from dormouse.sh via GitHub's `/latest/download/` redirect, which always resolves to the most recent release. | Asset | Filename | Purpose | |-------|----------|---------| -| Windows | `MouseTerm-windows-x64-setup.exe` | Download + Tauri updater | -| macOS | `MouseTerm-macos-aarch64.tar.gz` | Download + Tauri updater | -| Linux | `MouseTerm-linux-x86_64.AppImage` | Download + Tauri updater | +| Windows | `Dormouse-windows-x64-setup.exe` | Download + Tauri updater | +| macOS | `Dormouse-macos-aarch64.tar.gz` | Download + Tauri updater | +| Linux | `Dormouse-linux-x86_64.AppImage` | Download + Tauri updater | ### Download hotlinks -The mouseterm.com download page can link directly to the latest release with no server-side logic: +The dormouse.sh download page can link directly to the latest release with no server-side logic: ``` -https://github.com/diffplug/mouseterm/releases/latest/download/MouseTerm-windows-x64-setup.exe -https://github.com/diffplug/mouseterm/releases/latest/download/MouseTerm-macos-aarch64.tar.gz -https://github.com/diffplug/mouseterm/releases/latest/download/MouseTerm-linux-x86_64.AppImage +https://github.com/diffplug/mouseterm/releases/latest/download/Dormouse-windows-x64-setup.exe +https://github.com/diffplug/mouseterm/releases/latest/download/Dormouse-macos-aarch64.tar.gz +https://github.com/diffplug/mouseterm/releases/latest/download/Dormouse-linux-x86_64.AppImage ``` -These can later be migrated to `mouseterm.com/download/...` URLs backed by Cloudflare R2 (for analytics) without changing anything in the app — only the website links and the updater endpoint URL in `tauri.conf.json` would change. +These can later be migrated to `dormouse.sh/download/...` URLs backed by Cloudflare R2 (for analytics) without changing anything in the app — only the website links and the updater endpoint URL in `tauri.conf.json` would change. ## Tauri auto-updater @@ -172,7 +172,7 @@ Design notes that aren't obvious from the files: ### Update manifest (`standalone-latest.json`) -Generated by the local script after signing. The script writes it to `website/public/standalone-latest.json` so it's served from `mouseterm.com/standalone-latest.json` via Cloudflare Pages. This gives us request analytics on update checks. +Generated by the local script after signing. The script writes it to `website/public/standalone-latest.json` so it's served from `dormouse.sh/standalone-latest.json` via Cloudflare Pages. This gives us request analytics on update checks. ```json { @@ -181,22 +181,22 @@ Generated by the local script after signing. The script writes it to `website/pu "pub_date": "2026-03-25T12:00:00Z", "platforms": { "windows-x86_64": { - "url": "https://github.com/diffplug/mouseterm/releases/download/v0.1.0/MouseTerm-windows-x64-setup.exe", + "url": "https://github.com/diffplug/mouseterm/releases/download/v0.1.0/Dormouse-windows-x64-setup.exe", "signature": "<contents of .sig file>" }, "darwin-aarch64": { - "url": "https://github.com/diffplug/mouseterm/releases/download/v0.1.0/MouseTerm-macos-aarch64.tar.gz", + "url": "https://github.com/diffplug/mouseterm/releases/download/v0.1.0/Dormouse-macos-aarch64.tar.gz", "signature": "<contents of .sig file>" }, "linux-x86_64": { - "url": "https://github.com/diffplug/mouseterm/releases/download/v0.1.0/MouseTerm-linux-x86_64.AppImage", + "url": "https://github.com/diffplug/mouseterm/releases/download/v0.1.0/Dormouse-linux-x86_64.AppImage", "signature": "<contents of .sig file>" } } } ``` -Note: the update manifest URLs include the version in the *path* (`/v0.1.0/`) but the *filenames* are stable. The manifest itself is served from `mouseterm.com/standalone-latest.json` — Cloudflare Pages analytics tracks every update check. +Note: the update manifest URLs include the version in the *path* (`/v0.1.0/`) but the *filenames* are stable. The manifest itself is served from `dormouse.sh/standalone-latest.json` — Cloudflare Pages analytics tracks every update check. ## Changelog diff --git a/docs/specs/glossary.md b/docs/specs/glossary.md index fac12ba3..e72cda88 100644 --- a/docs/specs/glossary.md +++ b/docs/specs/glossary.md @@ -1,6 +1,6 @@ # Glossary -This glossary is the canonical vocabulary for states, entities, and transitions in mouseterm. Every other spec defers to this one when naming a state or a verb. When writing code or prose, pick names from here first. +This glossary is the canonical vocabulary for states, entities, and transitions in Dormouse. Every other spec defers to this one when naming a state or a verb. When writing code or prose, pick names from here first. ## The core idea diff --git a/docs/specs/layout.md b/docs/specs/layout.md index c02f3f45..4c279c8a 100644 --- a/docs/specs/layout.md +++ b/docs/specs/layout.md @@ -277,7 +277,7 @@ Pane IDs are session IDs. `TerminalPane` calls `getOrCreateTerminal(id)` on Reac - **Resume**: `resumeTerminal` creates xterm entry and writes replay data without spawning a new PTY. Used when the webview is recreated while the host retains Live PTYs (Link: Severed → Resuming → Live). - **Restore**: `restoreTerminal` creates xterm entry and spawns a new PTY with saved cwd and scrollback. Used on cold start from a saved Snapshot (Link: Cold → Live). - **Untouched**: new `getOrCreateTerminal` sessions start untouched. `isUntouched(id)` exposes the flag, and user-originated PTY input clears it via the registry input paths. Resume/restore seed the persisted flag; missing legacy snapshot data defaults to touched (`false`) so close confirmation remains conservative. -- **Shell selection replacement**: the standalone shell dropdown and VS Code shell picker send `mouseterm:new-terminal` with `replaceUntouched` when the selected shell type changes. `Wall` always creates a new session id for that request. If the currently selected pane or door is untouched, the new terminal is inserted in the same dockview position (`direction: 'within'`; doors first reattach through the normal restore path), the old untouched session is disposed, and the old panel is removed without kill confirmation. If the selected terminal is touched or no terminal is selected, the request spawns a new pane near the active panel. Announced shell-selection spawns show a transient pane-anchored notice such as `Switched to zsh` or `Opened bash`. +- **Shell selection replacement**: the standalone shell dropdown and VS Code shell picker send `dormouse:new-terminal` with `replaceUntouched` when the selected shell type changes. `Wall` always creates a new session id for that request. If the currently selected pane or door is untouched, the new terminal is inserted in the same dockview position (`direction: 'within'`; doors first reattach through the normal restore path), the old untouched session is disposed, and the old panel is removed without kill confirmation. If the selected terminal is touched or no terminal is selected, the request spawns a new pane near the active panel. Announced shell-selection spawns show a transient pane-anchored notice such as `Switched to zsh` or `Opened bash`. - During resume/restore replay, xterm.js may emit terminal-generated replies for OSC/CSI/DCS queries that were embedded in saved output. The registry drops those replay-time replies before they reach the new shell. This filter is limited to query/focus reports, and must not swallow user keyboard escape sequences such as arrows, function keys, or bracketed paste. - **mount / unmount (DOM)**: `mountElement` reparents the persistent DOM element into a container; `unmountElement` removes it. The Registry entry survives. - **Dispose**: `disposeSession` kills the PTY, disposes xterm, removes the registry entry. Only called on explicit kill (`x`). @@ -303,7 +303,7 @@ Each session also carries `TerminalPaneState` from `docs/specs/terminal-state.md ## Theme -Custom `mousetermTheme` extends dockview's `themeAbyss`: +Custom `dormouseTheme` extends dockview's `themeAbyss`: - `gap: 6` — 6px between groups in both directions - `dndOverlayMounting: 'absolute'`, `dndPanelOverlay: 'group'` - Pane header height: `--dv-tabs-and-actions-container-height: 30px` diff --git a/docs/specs/mobile-ui.md b/docs/specs/mobile-ui.md index 8504e5c7..0c5a7156 100644 --- a/docs/specs/mobile-ui.md +++ b/docs/specs/mobile-ui.md @@ -14,7 +14,7 @@ The app should feel like a lightweight mobile terminal playground. It does not need remote sessions, SSH, user accounts, or production infrastructure. The website `/tether` prototype exposes a small floating theme switcher above -the terminal. It uses the shared MouseTerm `ThemePicker`. +the terminal. It uses the shared Dormouse `ThemePicker`. `/tether` uses the same fake playground terminal stack as `/playground`: `PlaygroundShellRegistry` attaches a `TutorialShell` to every spawned pane, the @@ -102,7 +102,7 @@ Touch modes: | Mode | Button label | Icon | Availability | Behavior | | --- | --- | --- | --- | --- | | Gestures | `Gestures` | `HandPointingIcon` | Always available | Pane-content touches, pen presses, and primary mouse/trackpad clicks open the Gesture mode radial menu. | -| Text selection | `Select` | `CursorTextIcon` | Always available | Touches are reserved for terminal text selection and copy/paste. If the TUI is capturing mouse events, MouseTerm activates mouse override for the active pane. | +| Text selection | `Select` | `CursorTextIcon` | Always available | Touches are reserved for terminal text selection and copy/paste. If the TUI is capturing mouse events, Dormouse activates mouse override for the active pane. | | Mouse | `Mouse` | `CursorClickIcon` | Only when the active TUI is capturing mouse events | Touches are passed through as terminal mouse input. | Default touch mode is **Gestures**. @@ -285,7 +285,7 @@ Gesture action mappings: | PgUp | `\x1B[5~` | | k | `k` | | Backspace | `\x7F` | -| Paste | Existing MouseTerm paste flow for the active pane | +| Paste | Existing Dormouse paste flow for the active pane | | n | `n` | | ◀ | `\x1B[D` | | Home | `\x1B[H` | @@ -450,7 +450,7 @@ Prototype behavior: Build exactly this: * One terminal playground screen. -* Floating theme switcher using the shared MouseTerm theme picker. +* Floating theme switcher using the shared Dormouse theme picker. * Touch mode selector: ```text diff --git a/docs/specs/shortcuts.md b/docs/specs/shortcuts.md index bdde8f17..90d48f96 100644 --- a/docs/specs/shortcuts.md +++ b/docs/specs/shortcuts.md @@ -1,8 +1,8 @@ # Keyboard Shortcuts -Complete reference for mouseterm's keyboard shortcuts. Shortcuts are grouped by the mode/context in which they apply. +Complete reference for Dormouse's keyboard shortcuts. Shortcuts are grouped by the mode/context in which they apply. -mouseterm has two modes: +Dormouse has two modes: - **Workspace mode** (a.k.a. "command" mode internally) — keys drive pane layout. - **Terminal mode** (a.k.a. "passthrough" mode) — keys go to the running program, except copy/paste and the mode-switch gesture. diff --git a/docs/specs/terminal-state.md b/docs/specs/terminal-state.md index 60ab1616..c4c946e9 100644 --- a/docs/specs/terminal-state.md +++ b/docs/specs/terminal-state.md @@ -4,7 +4,7 @@ ## Goal -MouseTerm models terminal panes by: +Dormouse models terminal panes by: - latest reported working directory - current command line diff --git a/docs/specs/theme.md b/docs/specs/theme.md index b8dccca6..2e01c91d 100644 --- a/docs/specs/theme.md +++ b/docs/specs/theme.md @@ -1,15 +1,15 @@ # Theme Spec -MouseTerm's theme contract is intentionally small: render the terminal chrome +Dormouse's theme contract is intentionally small: render the terminal chrome with VSCode-appropriate surfaces, and render terminal content with theme-appropriate xterm.js colors. VSCode extension mode gets `--vscode-*` variables from VSCode. Standalone and website mode apply the same shape of variables to `document.body` with -`applyTheme()` from a bundled or installed MouseTerm theme. Both paths run the +`applyTheme()` from a bundled or installed Dormouse theme. Both paths run the same consumed-token resolver from `lib/src/lib/themes/vscode-color-resolver.ts` so omitted theme JSON keys behave like VSCode registry defaults before -MouseTerm renders. +Dormouse renders. ## Surface hierarchy @@ -48,7 +48,7 @@ That is accepted; terminal content still uses the theme's terminal palette. ## Runtime model -MouseTerm has two theme layers: +Dormouse has two theme layers: 1. `--vscode-*` variables hold imported or host-provided VSCode color data. 2. `--color-*` variables in `lib/src/theme.css` provide semantic Tailwind @@ -58,7 +58,7 @@ MouseTerm has two theme layers: missing consumed variables through the VSCode resolver, and adds either `vscode-light` or `vscode-dark` for consumers that need the theme type. In real VSCode webviews, `installVscodeThemeVarResolver()` runs before React renders; -it reads host-provided variables, materializes only missing MouseTerm-consumed +it reads host-provided variables, materializes only missing Dormouse-consumed variables on `body.style`, and removes stale materialized variables when the host starts providing a real value. @@ -69,10 +69,10 @@ declarations as the runtime source of truth. `theme.css` must not contain hardcoded color defaults or `var(..., fallback)` chains. Runtime hosts plus the shared resolver are responsible for providing -the consumed `--vscode-*` variables before MouseTerm renders. +the consumed `--vscode-*` variables before Dormouse renders. VSCode color IDs with `null` registry defaults need component-equivalent -materialization because MouseTerm consumes them through direct CSS variables. +materialization because Dormouse consumes them through direct CSS variables. Important cases: - `list.inactiveSelectionForeground` resolves to normal foreground @@ -81,7 +81,7 @@ Important cases: where an inactive selected row does not force active-selection white text. - Null foregrounds inherit the nearest normal foreground. - Null backgrounds inherit the relevant surface. -- Null border colors materialize as `transparent` so MouseTerm's existing +- Null border colors materialize as `transparent` so Dormouse's existing border geometry does not accidentally draw in `currentColor`. ## Terminal color contract @@ -106,7 +106,7 @@ terminals. `terminal-registry.ts` remains the public facade for callers. ## Theme data -Bundled and installed themes are represented by `MouseTermTheme` objects in +Bundled and installed themes are represented by `DormouseTheme` objects in `lib/src/lib/themes/`. A theme's `vars` map contains only consumed `--vscode-*` variables plus resolver dependencies. `convertVscodeThemeColors()` filters imported VSCode theme JSON to `CONSUMED_VSCODE_KEYS`; themes used @@ -134,15 +134,15 @@ rings outside a full Wall instance. ## Theme debugger -MouseTerm includes a diagnostic-only Theme Debugger shared by VSCode, +Dormouse includes a diagnostic-only Theme Debugger shared by VSCode, standalone, and the website playground. It never mutates theme storage or terminal colors. It captures the current DOM-visible theme state and shows: -- active MouseTerm theme metadata when `applyTheme()` is the source +- active Dormouse theme metadata when `applyTheme()` is the source (standalone/playground); real VSCode webviews show only the inferred VSCode theme kind because VSCode exposes CSS variables, not raw built-in theme JSON. - visible `--vscode-*` variables, marked as host/theme-provided or - MouseTerm-materialized. + Dormouse-materialized. - resolver traces for every resolvable consumed variable: provided value, registry default for the current kind, null-default fallback path, final resolved value, and origin. @@ -156,8 +156,8 @@ Standalone, playground, and the website `/tether` prototype expose the debugger as `Debug current theme` in the `ThemePicker` menu. `/tether` uses the same picker in the desktop page header and as a floating control above the mobile terminal prototype, both with the Kimbie Dark default theme fallback. VSCode -opens it through the `mouseterm.debugTheme` command and the -`mouseterm:openThemeDebugger` extension-to-webview message. The debugger's +opens it through the `dormouse.debugTheme` command and the +`dormouse:openThemeDebugger` extension-to-webview message. The debugger's copied report is a shareable text dump of the same snapshot. ## Maintainer checklist @@ -171,7 +171,7 @@ When changing theme behavior: dependency used by chrome, terminal rendering, selection UI, theme-picker inline styles, or resolver fallback paths. - Keep xterm.js terminal colors sourced from `--vscode-terminal-*` variables, - not from MouseTerm chrome tokens. + not from Dormouse chrome tokens. - Keep debugger dynamic-pick reporting and runtime dynamic-palette picks sharing `pickDoorPair()` and `pickFocusRing()`; do not fork those rules in UI code. - Do not add hardcoded color defaults or CSS variable fallback chains to diff --git a/docs/specs/transport.md b/docs/specs/transport.md index dfe2879c..26a99b9b 100644 --- a/docs/specs/transport.md +++ b/docs/specs/transport.md @@ -52,7 +52,7 @@ Both are capped at 1M chars per PTY. When the cap is reached, oldest chunks are ``` 1. Webview becomes visible (or panel deserializes). -2. Webview sends: { type: 'mouseterm:init' }. +2. Webview sends: { type: 'dormouse:init' }. 3. Host responds with: - { type: 'pty:list', ptys: [{ id, alive, exitCode }] } // all owned PTYs - { type: 'pty:replay', id, data } // buffered output per PTY @@ -60,7 +60,7 @@ Both are capped at 1M chars per PTY. When the cap is reached, oldest chunks are 5. If the saved session covers those live PTYs, the frontend uses the saved dockview layout when its visible panels match and reattaches saved minimized doors; minimized PTYs are registered but remain doors instead of visible panes. ``` -For cold restore (no live PTYs), the webview falls back to saved session state: spawns new PTYs in saved CWDs using the currently selected MouseTerm shell, injects saved scrollback (with trailing newline to avoid the zsh `%` artifact), and restores dockview layout. The entry module (`reconnect.ts`) uses a 500ms timeout when waiting for the PTY list. +For cold restore (no live PTYs), the webview falls back to saved session state: spawns new PTYs in saved CWDs using the currently selected Dormouse shell, injects saved scrollback (with trailing newline to avoid the zsh `%` artifact), and restores dockview layout. The entry module (`reconnect.ts`) uses a 500ms timeout when waiting for the PTY list. ## Message protocol @@ -77,9 +77,9 @@ Message types live in `vscode-ext/src/message-types.ts` (the canonical schema; o | `pty:getCwd` | Query PTY working directory (request-response via requestId) | | `pty:getScrollback` | Query PTY scrollback buffer (request-response via requestId) | | `pty:getShells` | Query available shells (request-response via requestId) | -| `mouseterm:init` | Trigger resume: get PTY list + replay data | -| `mouseterm:saveState` | Frontend persisting session state | -| `mouseterm:flushSessionSaveDone` | Ack for host-triggered flush (matched by requestId) | +| `dormouse:init` | Trigger resume: get PTY list + replay data | +| `dormouse:saveState` | Frontend persisting session state | +| `dormouse:flushSessionSaveDone` | Ack for host-triggered flush (matched by requestId) | | `alert:toggle` | Toggle alert enabled/disabled for a PTY | | `alert:disable` | Disable alert for a PTY | | `alert:dismiss` | Dismiss ringing alert | @@ -99,15 +99,15 @@ Message types live in `vscode-ext/src/message-types.ts` (the canonical schema; o | `pty:data` | PTY output after supported OSC sequences have been parsed/stripped (routed only to owning router) | | `pty:exit` | PTY process exited (with exitCode) | | `terminal:semanticEvents` | Normalized CWD/title/prompt/command events parsed in the host from live PTY data | -| `pty:list` | List of all resumable PTYs (response to `mouseterm:init`) | -| `pty:replay` | Buffered raw output since spawn (response to `mouseterm:init`); the webview parses semantic OSCs during replay reconstruction without triggering alerts | +| `pty:list` | List of all resumable PTYs (response to `dormouse:init`) | +| `pty:replay` | Buffered raw output since spawn (response to `dormouse:init`); the webview parses semantic OSCs during replay reconstruction without triggering alerts | | `pty:cwd` | CWD query response (matched by requestId) | | `pty:scrollback` | Scrollback query response (matched by requestId) | | `pty:shells` | Available shells list response (matched by requestId) | -| `mouseterm:newTerminal` | Host/UI request to spawn a terminal. Payload may include `shell`, `args`, display `name`, `replaceUntouched`, and `announce`; the webview replaces the selected untouched terminal in-place only when `replaceUntouched` is true, otherwise it spawns a new pane. | -| `mouseterm:selectedShell` | Update the webview's default shell options for later split/spawn/restore paths. | -| `mouseterm:flushSessionSave` | Request webview to save state now (host shutdown trigger, matched by requestId) | -| `mouseterm:openThemeDebugger` | Command-triggered request to open the shared theme debugger dialog | +| `dormouse:newTerminal` | Host/UI request to spawn a terminal. Payload may include `shell`, `args`, display `name`, `replaceUntouched`, and `announce`; the webview replaces the selected untouched terminal in-place only when `replaceUntouched` is true, otherwise it spawns a new pane. | +| `dormouse:selectedShell` | Update the webview's default shell options for later split/spawn/restore paths. | +| `dormouse:flushSessionSave` | Request webview to save state now (host shutdown trigger, matched by requestId) | +| `dormouse:openThemeDebugger` | Command-triggered request to open the shared theme debugger dialog | | `alert:state` | Alert state change (projected status, watchingEnabled, todo, notification, attentionDismissedRing) | The OSC parsing/stripping rules that produce `pty:data` and `terminal:semanticEvents` are specified in `docs/specs/OSC.md`. diff --git a/docs/specs/tutorial.md b/docs/specs/tutorial.md index fe49e6d6..3bdc97f1 100644 --- a/docs/specs/tutorial.md +++ b/docs/specs/tutorial.md @@ -1,6 +1,6 @@ # Playground Tutorial -At the `/playground` route on the website. Interactive TUI: each item starts pending, the first incomplete item is marked as active, and completed items become green checks when MouseTerm detects the corresponding action. +At the `/playground` route on the website. Interactive TUI: each item starts pending, the first incomplete item is marked as active, and completed items become green checks when Dormouse detects the corresponding action. ## Architecture @@ -66,7 +66,7 @@ The detector subscribes to `subscribeToActivity()` and tracks per-id `(status, w | `al-todo-clear` | Press passthrough Enter to clear the TODO | `todo` transitions `true → false` | | `al-todo-manual` | Manually add a TODO (`t` or right-click) | `todo` transitions `false → true` while previous status was NOT `ALERT_RINGING` | -The detector remembers the most recent pane whose `watchingEnabled` flag is true, even when projected `status` is currently owned by protocol or command-exit alert tracks. The Alert section view shows a runner-local instruction: "Press `s` here to start a fake busy task." `s` is **not** a real MouseTerm shortcut; it is intercepted by `TutRunner` only while the Alert section is open. When pressed, the runner does two things: +The detector remembers the most recent pane whose `watchingEnabled` flag is true, even when projected `status` is currently owned by protocol or command-exit alert tracks. The Alert section view shows a runner-local instruction: "Press `s` here to start a fake busy task." `s` is **not** a real Dormouse shortcut; it is intercepted by `TutRunner` only while the Alert section is open. When pressed, the runner does two things: 1. Resolves that pane to its current PTY session id, then calls `adapter.pumpActivity(sessionId, BUSY_DEMO_DURATION_MS, 800)` — drives the alert-manager's activity monitor on the same WATCHING-enabled session with **no text output**, so the bell tilts to BUSY without scrolling any scenario text. The session id is resolved at trigger time so `Cmd/Ctrl+Arrow` swaps do not leave the tutorial pumping an old pane id. If no WATCHING-enabled pane is known, the runner falls back to `PANE_BOXED` (the changelog pane). `BUSY_DEMO_DURATION_MS` is `cfg.alert.userAttention + 250` so silence begins after the attention idle window has expired, with a small scheduler-jitter guard; otherwise the "user is looking at this pane" check inside `ActivityMonitor.startNeedsAttentionConfirmTimer` would suppress the ring rather than let it fire. 2. Animates a countdown in-place where the "Press s…" hint was: `⠋ Fake task will finish in N seconds.` ticking down to 1, then a static `✓ Fake task finished. Press s to start another one.` once the activity stops. Detection is purely timing-based via the existing `ActivityMonitor`, so no shell integration is required. diff --git a/docs/specs/vscode.md b/docs/specs/vscode.md index f0b9a067..1ce26b8d 100644 --- a/docs/specs/vscode.md +++ b/docs/specs/vscode.md @@ -1,10 +1,10 @@ -# MouseTerm VS Code Integration Spec +# Dormouse VS Code Integration Spec > See `docs/specs/transport.md` for the PTY lifecycle, message protocol, persisted-session types, and adapter-agnostic invariants that VS Code shares with the standalone and fake adapters. This spec covers the VS Code-specific layer: panel/view registration, persistence APIs, theme integration, CSP, build, and dream-architecture commands. ## What's built -MouseTerm has two hosting modes: a `WebviewView` in the bottom panel (alongside Terminal, Problems, Output) and `WebviewPanel` editor tabs (via `mouseterm.open`, supports multiple instances). Both restore across "Developer: Reload Window". PTY lifecycle is fully decoupled from the webview — PTYs live in the extension host via `pty-manager.ts`, survive panel visibility toggling, and replay buffered output on **resume**. Session persistence works across cold **restore**: pane layout, CWD, scrollback, alert state (enabled/disabled + todo), and resume commands are saved and restored on cold start. The view uses `workspaceState` for persistence; editor panels use VS Code's per-panel `vscode.setState()` so multiple panels don't clobber each other. Alert state is merged into every periodic save (not just deactivate) so it survives even if VS Code kills the extension host before deactivate completes. A `WebviewPanelSerializer` handles editor tab restoration; `onWebviewPanel:mouseterm` activation event ensures the extension activates early enough. Theme integration uses VSCode `--vscode-*` tokens plus MouseTerm semantic `--color-*` tokens, with a small resolver that materializes missing consumed VSCode colors from registry defaults. CSP is strict with nonce-gated scripts. +Dormouse has two hosting modes: a `WebviewView` in the bottom panel (alongside Terminal, Problems, Output) and `WebviewPanel` editor tabs (via `dormouse.open`, supports multiple instances). Both restore across "Developer: Reload Window". PTY lifecycle is fully decoupled from the webview — PTYs live in the extension host via `pty-manager.ts`, survive panel visibility toggling, and replay buffered output on **resume**. Session persistence works across cold **restore**: pane layout, CWD, scrollback, alert state (enabled/disabled + todo), and resume commands are saved and restored on cold start. The view uses `workspaceState` for persistence; editor panels use VS Code's per-panel `vscode.setState()` so multiple panels don't clobber each other. Alert state is merged into every periodic save (not just deactivate) so it survives even if VS Code kills the extension host before deactivate completes. A `WebviewPanelSerializer` handles editor tab restoration; `onWebviewPanel:dormouse` activation event ensures the extension activates early enough. Theme integration uses VSCode `--vscode-*` tokens plus Dormouse semantic `--color-*` tokens, with a small resolver that materializes missing consumed VSCode colors from registry defaults. CSP is strict with nonce-gated scripts. **Architecture:** @@ -70,43 +70,43 @@ Universal PTY/transport invariants live in `docs/specs/transport.md`. The rules - **PTY ownership tracking.** Each router tracks its PTYs in `ownedPtyIds`. A module-level `globalOwnedPtyIds` set prevents a resuming router from stealing PTYs owned by another webview. - **mergeAlertStates on every save path.** Both the frontend periodic save (`onSaveState` callback) and the backend deactivate refresh (`refreshSavedSessionStateFromPtys`) must merge current alert states. Missing this causes alert state to revert on restore. - **retainContextWhenHidden.** Set on both `WebviewPanel` (editor tabs) and `WebviewView` (bottom panel) so that xterm.js DOM, scrollback, and PTY subscriptions survive panel hide/show without going through a resume. -- **Two save sources.** Session state is saved from two places: the frontend (debounced 500ms + 30s interval via `mouseterm:saveState`) and the backend (deactivate flushes webviews then refreshes from live PTYs). Both paths must produce consistent state. +- **Two save sources.** Session state is saved from two places: the frontend (debounced 500ms + 30s interval via `dormouse:saveState`) and the backend (deactivate flushes webviews then refreshes from live PTYs). Both paths must produce consistent state. ### Extension manifest (current) ```jsonc { "activationEvents": [ - "onView:mouseterm.view", - "onWebviewPanel:mouseterm" + "onView:dormouse.view", + "onWebviewPanel:dormouse" ], "contributes": { "commands": [ - { "command": "mouseterm.focus", "title": "MouseTerm: Focus", + { "command": "dormouse.focus", "title": "Dormouse: Focus", "icon": { "light": "icon-tiny-light.png", "dark": "icon-tiny-dark.png" } }, - { "command": "mouseterm.open", "title": "MouseTerm: Open in Editor" }, - { "command": "mouseterm.debugTheme", "title": "MouseTerm: Debug Theme" }, - { "command": "mouseterm.newTerminal", "title": "MouseTerm: New Terminal", + { "command": "dormouse.open", "title": "Dormouse: Open in Editor" }, + { "command": "dormouse.debugTheme", "title": "Dormouse: Debug Theme" }, + { "command": "dormouse.newTerminal", "title": "Dormouse: New Terminal", "icon": "$(add)" }, - { "command": "mouseterm.selectShell", "title": "MouseTerm: Select Shell", + { "command": "dormouse.selectShell", "title": "Dormouse: Select Shell", "icon": "$(gear)" } ], "menus": { "view/title": [ - { "command": "mouseterm.selectShell", "group": "navigation@1", - "when": "view == mouseterm.view" }, - { "command": "mouseterm.newTerminal", "group": "navigation@2", - "when": "view == mouseterm.view" } + { "command": "dormouse.selectShell", "group": "navigation@1", + "when": "view == dormouse.view" }, + { "command": "dormouse.newTerminal", "group": "navigation@2", + "when": "view == dormouse.view" } ] }, "viewsContainers": { "panel": [ - { "id": "mouseterm-panel", "title": "MouseTerm", "icon": "$(terminal)" } + { "id": "dormouse-panel", "title": "Dormouse", "icon": "$(terminal)" } ] }, "views": { - "mouseterm-panel": [ - { "id": "mouseterm.view", "name": "MouseTerm", "type": "webview" } + "dormouse-panel": [ + { "id": "dormouse.view", "name": "Dormouse", "type": "webview" } ] } } @@ -124,16 +124,16 @@ Extension Host (always running while extension is active) │ ├── pty-2 (Process: Live) │ └── pty-3 (Process: Exited) │ -├── WebviewView "MouseTerm" (bottom panel) +├── WebviewView "Dormouse" (bottom panel) │ └── message-router: owns pty-1, pty-2 │ -└── WebviewPanel "MouseTerm" (editor tab, optional) +└── WebviewPanel "Dormouse" (editor tab, optional) └── message-router: owns pty-3 ``` VS Code-specific consequences: -- Hiding the MouseTerm panel doesn't kill its PTYs. +- Hiding the Dormouse panel doesn't kill its PTYs. - VS Code toggling the panel visibility doesn't destroy sessions. - Multiple VS Code windows each get their own extension host process, and therefore their own pty-host child process. @@ -141,23 +141,23 @@ PTY lifecycle, buffering, the reconnection sequence, and the full message protoc ### Shell selection -The VS Code view title contributes `MouseTerm: Select Shell` and `MouseTerm: New Terminal`. The selected shell name is mirrored into the `WebviewView.description`, and `mouseterm:selectedShell` keeps the webview's default-shell slot current for split/spawn/restore paths. +The VS Code view title contributes `Dormouse: Select Shell` and `Dormouse: New Terminal`. The selected shell name is mirrored into the `WebviewView.description`, and `dormouse:selectedShell` keeps the webview's default-shell slot current for split/spawn/restore paths. -`mouseterm.newTerminal` focuses the MouseTerm view and posts `mouseterm:newTerminal` with the currently selected shell. `mouseterm.selectShell` opens a QuickPick, saves the shell path globally or per workspace, applies the description/default-shell update, and, when the picked shell differs from the previous selection, focuses the view and posts `mouseterm:newTerminal` with `replaceUntouched: true` and `announce: true`. The shared `Wall` logic then replaces only a selected untouched terminal in-place; touched terminals cause an additional pane to be spawned instead. +`dormouse.newTerminal` focuses the Dormouse view and posts `dormouse:newTerminal` with the currently selected shell. `dormouse.selectShell` opens a QuickPick, saves the shell path globally or per workspace, applies the description/default-shell update, and, when the picked shell differs from the previous selection, focuses the view and posts `dormouse:newTerminal` with `replaceUntouched: true` and `announce: true`. The shared `Wall` logic then replaces only a selected untouched terminal in-place; touched terminals cause an additional pane to be spawned instead. ### Serialization and restore `WebviewPanelSerializer` is registered so VS Code can restore editor panels after restart: ``` -activationEvents: ["onWebviewPanel:mouseterm"] +activationEvents: ["onWebviewPanel:dormouse"] ``` The persisted-session shape (`PersistedSession` / `PersistedPane` / `PersistedAlertState` / `PersistedDoor`) lives in `docs/specs/transport.md`; it is shared with the standalone and fake adapters. **VS Code persistence flow:** -1. Frontend saves state periodically (debounced 500ms + 30s interval) via `mouseterm:saveState` message. +1. Frontend saves state periodically (debounced 500ms + 30s interval) via `dormouse:saveState` message. 2. Router's `onSaveState` callback merges in current alert states via `mergeAlertStates()`. 3. WebviewView writes to `workspaceState`; WebviewPanels persist via `vscode.setState()` (per-panel, no clobbering). 4. On deactivate: flush all sessions from webviews (1s timeout), then refresh from live PTYs (queries CWD + scrollback while processes are still alive). @@ -166,7 +166,7 @@ The persisted-session shape (`PersistedSession` / `PersistedPane` / `PersistedAl ### Theme integration -Two-layer CSS variable system: VS Code injects `--vscode-*` tokens; `lib/src/theme.css` maps them directly to semantic `--color-*` tokens for use in Tailwind utility classes. The webview entry point installs `installVscodeThemeVarResolver()` before React renders. That resolver reads VSCode-provided variables, materializes only missing MouseTerm-consumed variables on `body.style`, and watches `body`/`html` class and style mutations so theme changes recompute those materialized values. +Two-layer CSS variable system: VS Code injects `--vscode-*` tokens; `lib/src/theme.css` maps them directly to semantic `--color-*` tokens for use in Tailwind utility classes. The webview entry point installs `installVscodeThemeVarResolver()` before React renders. That resolver reads VSCode-provided variables, materializes only missing Dormouse-consumed variables on `body.style`, and watches `body`/`html` class and style mutations so theme changes recompute those materialized values. Example of the pattern: ```css @@ -176,14 +176,14 @@ Example of the pattern: --color-header-inactive-fg: var(--vscode-list-inactiveSelectionForeground); ``` -`theme.css` intentionally has no hardcoded color defaults or CSS variable fallback chains. The resolver duplicates VSCode registry defaults for the MouseTerm-consumed color IDs, including `null` default behavior where MouseTerm needs a concrete CSS variable. In particular, `list.inactiveSelectionForeground` resolves to normal foreground inheritance, not `list.activeSelectionForeground`; this matches VSCode's list/tree selected-row behavior for built-in Light. +`theme.css` intentionally has no hardcoded color defaults or CSS variable fallback chains. The resolver duplicates VSCode registry defaults for the Dormouse-consumed color IDs, including `null` default behavior where Dormouse needs a concrete CSS variable. In particular, `list.inactiveSelectionForeground` resolves to normal foreground inheritance, not `list.activeSelectionForeground`; this matches VSCode's list/tree selected-row behavior for built-in Light. A `MutationObserver` in `lib/src/lib/terminal-theme.ts` watches for VS Code theme changes on `body`/`html` (class and style attribute mutations) and live-updates all xterm.js instances. The `terminal-registry.ts` facade still exposes the public lifecycle APIs. The theme resolver has its own observer on the same attributes so derived `--vscode-*` variables stay in sync before xterm rereads the terminal palette. -`mouseterm.debugTheme` focuses the MouseTerm WebviewView and posts -`mouseterm:openThemeDebugger` to the webview. `VSCodeAdapter` converts that +`dormouse.debugTheme` focuses the Dormouse WebviewView and posts +`dormouse:openThemeDebugger` to the webview. `VSCodeAdapter` converts that message into the browser event consumed by the shared Theme Debugger. The -debugger traces VSCode-exposed `--vscode-*` variables and MouseTerm +debugger traces VSCode-exposed `--vscode-*` variables and Dormouse materialized fallbacks, but it does not attempt to read raw built-in VSCode theme files. @@ -205,8 +205,8 @@ connect-src ${webview.cspSource}; ``` pnpm build:vscode = 1. pnpm --filter mouseterm-lib build (TypeScript compile) - 2. pnpm --filter mouseterm build:frontend (Vite: lib -> vscode-ext/media/) - 3. pnpm --filter mouseterm build (esbuild: extension.ts + pty-host.js -> dist/, + 2. pnpm --filter dormouse build:frontend (Vite: lib -> vscode-ext/media/) + 3. pnpm --filter dormouse build (esbuild: extension.ts + pty-host.js -> dist/, copy node-pty prebuilds -> dist/node-pty) pnpm dogfood:vscode = build + package VSIX + install locally @@ -226,16 +226,16 @@ The Vite config for the extension (`vscode-ext/vite.config.ts`) sets `root: ../l ### Context keys -Set context keys so menus and extensions can target MouseTerm state: +Set context keys so menus and extensions can target Dormouse state: ```typescript -// Set when any MouseTerm webview has focus +// Set when any Dormouse webview has focus vscode.commands.executeCommand('setContext', 'mouseterm.active', true); -// Set when MouseTerm is in passthrough/terminal mode (keys go to PTY) +// Set when Dormouse is in passthrough/terminal mode (keys go to PTY) vscode.commands.executeCommand('setContext', 'mouseterm.mode', 'terminal'); -// Set when MouseTerm is in normal/navigation mode (keys go to MouseTerm UI) +// Set when Dormouse is in normal/navigation mode (keys go to Dormouse UI) vscode.commands.executeCommand('setContext', 'mouseterm.mode', 'normal'); ``` @@ -243,8 +243,8 @@ vscode.commands.executeCommand('setContext', 'mouseterm.mode', 'normal'); | Command | Description | |---------|-------------| -| `mouseterm.focus` | Focus the MouseTerm panel view | -| `mouseterm.newPane` | Split a new pane in MouseTerm | +| `dormouse.focus` | Focus the Dormouse panel view | +| `mouseterm.newPane` | Split a new pane in Dormouse | | `mouseterm.closePane` | Close the focused pane | | `mouseterm.nextPane` | Focus next pane | | `mouseterm.prevPane` | Focus previous pane | @@ -255,7 +255,7 @@ vscode.commands.executeCommand('setContext', 'mouseterm.mode', 'normal'); ### Not yet implemented -- `TerminalProfileProvider` not registered — MouseTerm doesn't appear in the terminal `+` dropdown +- `TerminalProfileProvider` not registered — Dormouse doesn't appear in the terminal `+` dropdown - Context keys not set (`mouseterm.active`, `mouseterm.mode`) — needed for conditional keybindings - Commands not registered: `mouseterm.newPane`, `closePane`, `nextPane`, `prevPane`, `enterTerminalMode`, `enterNormalMode`, `listSessions`, `reattach` - No status bar item showing active session count diff --git a/lib/src/components/Wall.tsx b/lib/src/components/Wall.tsx index edd07c1d..e94e6b60 100644 --- a/lib/src/components/Wall.tsx +++ b/lib/src/components/Wall.tsx @@ -84,7 +84,7 @@ export { TerminalPaneHeader } from './wall/TerminalPaneHeader'; // --- Theme --- -const mousetermTheme: DockviewTheme = { +const dormouseTheme: DockviewTheme = { ...themeAbyss, name: 'mouseterm', gap: 6, @@ -745,7 +745,7 @@ export function Wall({ components={components} tabComponents={tabComponents} onReady={handleReady} - theme={mousetermTheme} + theme={dormouseTheme} singleTabMode="fullwidth" /> <WorkspaceSelectionOverlay apiRef={apiRef} selectedId={selectedId} selectedType={selectedType} mode={mode} overlayElRef={overlayElRef} /> diff --git a/lib/src/stories/MobileTerminalUi.stories.tsx b/lib/src/stories/MobileTerminalUi.stories.tsx index 63a75c98..6466fe7a 100644 --- a/lib/src/stories/MobileTerminalUi.stories.tsx +++ b/lib/src/stories/MobileTerminalUi.stories.tsx @@ -44,11 +44,11 @@ const meta: Meta<typeof MobileTerminalUi> = { export default meta; type Story = StoryObj<typeof MobileTerminalUi>; -const TETHER_WALL_PANE = 'storybook-tether-wall'; -const TETHER_WALL_SESSIONS: MobileWallSession[] = [{ id: TETHER_WALL_PANE, title: 'ascii-splash' }]; +const POCKET_WALL_PANE = 'storybook-pocket-wall'; +const POCKET_WALL_SESSIONS: MobileWallSession[] = [{ id: POCKET_WALL_PANE, title: 'ascii-splash' }]; -const TETHER_WALL_SCENARIO: FakeScenario = { - name: 'tether-wall-ascii-splash', +const POCKET_WALL_SCENARIO: FakeScenario = { + name: 'pocket-wall-ascii-splash', chunks: [{ delay: 0, data: [ @@ -137,14 +137,14 @@ function StoryFrame(args: MobileTerminalUiProps) { ); } -function TetherWallFrame(args: MobileTerminalUiProps) { +function PocketWallFrame(args: MobileTerminalUiProps) { const adapterRef = useRef<FakePtyAdapter | null>(null); if (!adapterRef.current) adapterRef.current = initPlatform('fake'); - const [activePaneId, setActivePaneId] = useState(TETHER_WALL_PANE); + const [activePaneId, setActivePaneId] = useState(POCKET_WALL_PANE); const [keyboardMode, setKeyboardMode] = useState<MobileTerminalKeyboardMode>( args.activeKeyboardMode ?? args.activeSection ?? args.defaultKeyboardMode ?? args.defaultSection ?? 'type', ); - const sessionItems = useMobileWallSessionItems(TETHER_WALL_SESSIONS, activePaneId); + const sessionItems = useMobileWallSessionItems(POCKET_WALL_SESSIONS, activePaneId); return ( <main className="fixed inset-0 bg-black"> @@ -153,7 +153,7 @@ function TetherWallFrame(args: MobileTerminalUiProps) { fillViewport terminal={( <MobileWall - sessions={TETHER_WALL_SESSIONS} + sessions={POCKET_WALL_SESSIONS} activeSessionId={activePaneId} onActiveSessionChange={setActivePaneId} onSessionMinimize={() => setKeyboardMode('sessions')} @@ -315,15 +315,15 @@ export const CursorTouchAvailable: Story = { render: (args) => <StoryFrame {...args} />, }; -export const TetherWall: Story = { +export const PocketWall: Story = { args: { defaultSection: 'type', }, parameters: { layout: 'fullscreen', - fakePty: { scenario: flattenScenario(TETHER_WALL_SCENARIO) }, + fakePty: { scenario: flattenScenario(POCKET_WALL_SCENARIO) }, }, - render: (args) => <TetherWallFrame {...args} />, + render: (args) => <PocketWallFrame {...args} />, }; export const GestureMenuOpened: Story = { diff --git a/lib/src/stories/Smoke.stories.tsx b/lib/src/stories/Smoke.stories.tsx index e5fbf27a..f8493b0e 100644 --- a/lib/src/stories/Smoke.stories.tsx +++ b/lib/src/stories/Smoke.stories.tsx @@ -130,7 +130,7 @@ function ThemeCheck() { <header className="grid gap-1"> <h1 className="text-lg font-semibold">Storybook Theme Smoke Test</h1> <p className="max-w-3xl text-sm text-muted"> - Verifies the resolved VSCode host variables, MouseTerm semantic tokens, and dynamic + Verifies the resolved VSCode host variables, Dormouse semantic tokens, and dynamic palette picks that Storybook injects for isolated stories. </p> </header> @@ -180,7 +180,7 @@ function ThemeCheck() { <VarTable rows={HOST_VARS} /> </div> <div className="grid content-start gap-3"> - <h2 className="text-sm font-semibold">MouseTerm Tokens</h2> + <h2 className="text-sm font-semibold">Dormouse Tokens</h2> <VarTable rows={SEMANTIC_VARS} /> </div> </section> diff --git a/lib/src/stories/UpdateDebugDialog.stories.tsx b/lib/src/stories/UpdateDebugDialog.stories.tsx index 27039e02..77da4cc3 100644 --- a/lib/src/stories/UpdateDebugDialog.stories.tsx +++ b/lib/src/stories/UpdateDebugDialog.stories.tsx @@ -24,7 +24,7 @@ function UpdateDebugDialogStory({ failure, body }: StoryArgs) { ); } -const ERROR = 'EACCES: permission denied at /Applications/MouseTerm.app'; +const ERROR = 'EACCES: permission denied at /Applications/Dormouse.app'; const BODY = [ '**App version**: 0.7.0 → 0.8.0', diff --git a/standalone/src-tauri/src/lib.rs b/standalone/src-tauri/src/lib.rs index 62e24fa1..721b020c 100644 --- a/standalone/src-tauri/src/lib.rs +++ b/standalone/src-tauri/src/lib.rs @@ -37,7 +37,7 @@ struct SidecarState { child: SharedChild, } -const LOG_FILE_ENV: &str = "MOUSETERM_LOG_FILE"; +const LOG_FILE_ENV: &str = "DORMOUSE_LOG_FILE"; fn log_timestamp() -> u64 { SystemTime::now() diff --git a/website/src/lib/tutorial-state.ts b/website/src/lib/tutorial-state.ts index a52ee542..210f4557 100644 --- a/website/src/lib/tutorial-state.ts +++ b/website/src/lib/tutorial-state.ts @@ -1,6 +1,6 @@ import { ALL_ITEM_IDS, ITEM_IDS, SECTIONS, type ItemId } from "./tut-items"; -const STORAGE_KEY = "mouseterm-tut-v3"; +const STORAGE_KEY = "dormouse-tut-v3"; const KNOWN_IDS: ReadonlySet<ItemId> = new Set(ITEM_IDS); export class TutorialState { From a2865e1c6036ff0aadeb72cbf4c5c99aa0cff86c Mon Sep 17 00:00:00 2001 From: Ned Twigg <ned.twigg@diffplug.com> Date: Fri, 15 May 2026 17:13:04 -0700 Subject: [PATCH 08/24] Update root package.json pnpm filters for renamed vscode-ext build:vscode and dogfood:vscode used --filter mouseterm to match the vscode-ext package name. That was renamed to "dormouse" in vscode-ext/package.json; update the filters to match. Workspace filters for mouseterm-lib, mouseterm-standalone, mouseterm-website, mouseterm-root remain unchanged (internal package names). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index d7d2e01a..af3e019d 100644 --- a/package.json +++ b/package.json @@ -8,10 +8,10 @@ "dev:lib": "pnpm --filter mouseterm-lib dev", "dev:standalone": "pnpm --filter mouseterm-standalone tauri dev", "dev:website": "pnpm --filter mouseterm-website dev", - "build:vscode": "pnpm --filter mouseterm-lib build && pnpm --filter mouseterm build:frontend && pnpm --filter mouseterm build", + "build:vscode": "pnpm --filter mouseterm-lib build && pnpm --filter dormouse build:frontend && pnpm --filter dormouse build", "build:standalone": "pnpm --filter mouseterm-standalone tauri build", "build:website": "pnpm --filter mouseterm-website build", - "dogfood:vscode": "pnpm run build:vscode && pnpm --filter mouseterm dogfood", + "dogfood:vscode": "pnpm run build:vscode && pnpm --filter dormouse dogfood", "dogfood:standalone": "bash standalone/scripts/dogfood.sh", "storybook": "pnpm --filter mouseterm-lib storybook", "bundle-themes": "node lib/scripts/bundle-themes.mjs" From 585f75f5d2b66aa91d80cb8dbab76d8a1eef07db Mon Sep 17 00:00:00 2001 From: Ned Twigg <ned.twigg@diffplug.com> Date: Fri, 15 May 2026 17:32:09 -0700 Subject: [PATCH 09/24] =?UTF-8?q?Rename=20pnpm=20workspace=20packages:=20m?= =?UTF-8?q?ouseterm-*=20=E2=86=92=20dormouse-*?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Workspace package names renamed (private packages, internal only — no published npm-registry impact): - mouseterm-root → dormouse-root (package.json) - mouseterm-lib → dormouse-lib (lib/package.json) - mouseterm-standalone → dormouse-standalone (standalone/package.json) - mouseterm-website → dormouse-website (website/package.json) - mouseterm-sidecar → dormouse-sidecar (standalone/sidecar/package.json) Cascading updates: Imports (TS/TSX): every `from "mouseterm-lib/…"` and dynamic `import("mouseterm-lib/…")` → `from "dormouse-lib/…"` across lib, standalone, and website source/test files. Path / alias mappings: - standalone/tsconfig.json, website/tsconfig.json, lib/tsconfig.app.json: path mappings keyed on "mouseterm-lib/*" → "dormouse-lib/*" - standalone/vite.config.ts, standalone/vitest.config.ts, website/vite.config.ts: alias keys - lib/.storybook/main.ts: any moduleNameMapper / alias `workspace:*` deps: - standalone/package.json + website/package.json: dependency entry "mouseterm-lib" → "dormouse-lib" pnpm `--filter` references: - root package.json scripts (build/build:vscode/dev:lib/dev:website/ dev:standalone/build:standalone/build:website/storybook) - .github/workflows/release.yml (--filter dormouse-lib test) - standalone/scripts/dogfood.sh - docs/specs/deploy.md, docs/specs/vscode.md, docs/specs/tutorial.md - .claude/settings.json permission allowlist patterns pnpm-lock.yaml regenerated via `pnpm install`. Verified: all 514 tests pass (lib 460, website 38, standalone 16); dev server serves /, /pocket, /playground with 200 OK and the build output reports `dormouse-root@ dev:website` / `dormouse-website@0.1.0 predev`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --- .claude/settings.json | 4 ++-- .github/workflows/release.yml | 2 +- docs/specs/deploy.md | 6 +++--- docs/specs/tutorial.md | 6 +++--- docs/specs/vscode.md | 2 +- lib/.storybook/main.ts | 2 +- lib/package.json | 2 +- lib/tsconfig.app.json | 2 +- package.json | 18 ++++++++--------- pnpm-lock.yaml | 4 ++-- standalone/package.json | 4 ++-- standalone/scripts/dogfood.sh | 4 ++-- standalone/sidecar/package.json | 2 +- standalone/src/main.tsx | 14 ++++++------- standalone/src/tauri-adapter.ts | 8 ++++---- standalone/src/updater.ts | 2 +- standalone/tsconfig.json | 2 +- standalone/vite.config.ts | 2 +- standalone/vitest.config.ts | 2 +- website/package.json | 4 ++-- website/src/lib/ascii-splash-runner.test.ts | 2 +- website/src/lib/ascii-splash-runner.ts | 4 ++-- website/src/lib/changelog-runner.ts | 4 ++-- website/src/lib/playground-shells.test.ts | 2 +- website/src/lib/playground-shells.ts | 2 +- website/src/lib/tut-detector.test.ts | 4 ++-- website/src/lib/tut-detector.ts | 10 +++++----- website/src/lib/tut-items.ts | 2 +- website/src/lib/tut-runner.test.ts | 2 +- website/src/lib/tut-runner.ts | 6 +++--- website/src/lib/tutorial-shell.ts | 2 +- website/src/pages/Home.tsx | 2 +- website/src/pages/Playground.tsx | 18 ++++++++--------- website/src/pages/Pocket.tsx | 22 ++++++++++----------- website/tsconfig.json | 2 +- website/vite.config.ts | 2 +- 36 files changed, 89 insertions(+), 89 deletions(-) diff --git a/.claude/settings.json b/.claude/settings.json index dc70c727..8aeb01f5 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -5,8 +5,8 @@ "Bash(npx tsc:*)", "Bash(magick identify:*)", "Bash(pnpm test:*)", - "Bash(pnpm --filter mouseterm-lib test:*)", - "Bash(pnpm --filter mouseterm-standalone test:*)", + "Bash(pnpm --filter dormouse-lib test:*)", + "Bash(pnpm --filter dormouse-standalone test:*)", "mcp__Claude_Preview__preview_screenshot" ] } diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 52e4a133..9e4197f9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -109,7 +109,7 @@ jobs: run: pnpm install --frozen-lockfile - name: Test lib - run: pnpm --filter mouseterm-lib test + run: pnpm --filter dormouse-lib test - name: Build frontend for VSCode run: pnpm --filter dormouse build:frontend diff --git a/docs/specs/deploy.md b/docs/specs/deploy.md index 73bed060..52c11953 100644 --- a/docs/specs/deploy.md +++ b/docs/specs/deploy.md @@ -87,7 +87,7 @@ Each matrix leg: Runs on `ubuntu-latest`: 1. Checkout, setup Node 22, pnpm 10 2. `pnpm install --frozen-lockfile` at the repo root -3. `pnpm --filter mouseterm-lib test` +3. `pnpm --filter dormouse-lib test` 4. `pnpm --filter dormouse build:frontend && pnpm --filter dormouse build` 5. `npx vsce package --no-dependencies` 6. Upload `.vsix` as artifact @@ -203,8 +203,8 @@ Note: the update manifest URLs include the version in the *path* (`/v0.1.0/`) bu A single `CHANGELOG.md` at the repo root, following [Keep a Changelog](https://keepachangelog.com/) format. The `[Unreleased]` section is promoted to `[X.Y.Z]` at release time. The release notes include both standalone and VSCode changes in one entry. The website changelog page imports generated data from `website/src/data/changelog.json`, but `CHANGELOG.md` is the source of truth and the JSON is gitignored. You do not normally run `website/scripts/generate-changelog.js` by hand: -- `pnpm --filter mouseterm-website build` runs it through the website `prebuild` script before Vite bundles the static site. -- `pnpm --filter mouseterm-website dev` and `pnpm --filter mouseterm-website test` also regenerate it through lifecycle scripts so clean checkouts work locally. +- `pnpm --filter dormouse-website build` runs it through the website `prebuild` script before Vite bundles the static site. +- `pnpm --filter dormouse-website dev` and `pnpm --filter dormouse-website test` also regenerate it through lifecycle scripts so clean checkouts work locally. If you edit `CHANGELOG.md` manually outside `/release-notes` and want to preview the generated data immediately, run `node website/scripts/generate-changelog.js`. Do not commit `website/src/data/changelog.json`. diff --git a/docs/specs/tutorial.md b/docs/specs/tutorial.md index 3bdc97f1..133c6c54 100644 --- a/docs/specs/tutorial.md +++ b/docs/specs/tutorial.md @@ -7,7 +7,7 @@ At the `/playground` route on the website. Interactive TUI: each item starts pen Three browser-side pieces in `website/src/lib/`, mirroring the pattern in `website/src/lib/ascii-splash-runner.ts` (xterm alt-screen + `FakePtyAdapter` boundary, no Node `terminal-kit` package): - **`tut-runner.ts`** (`TutRunner`) — alt-screen TUI. Subscribes to `TutorialState` and re-renders whenever progress changes. Routes input bytes via `FakePtyAdapter.writePty(id, …)`. -- **`tut-detector.ts`** (`TutDetector`) — wires app events to `TutorialState.markComplete(id)`. Subscribes to `DockviewApi.onDidActivePanelChange`, the `WallEvent` stream, the `subscribeToActivity` store from `mouseterm-lib/lib/terminal-registry`, and the `subscribeToMouseSelection` store from `mouseterm-lib/lib/mouse-selection`. +- **`tut-detector.ts`** (`TutDetector`) — wires app events to `TutorialState.markComplete(id)`. Subscribes to `DockviewApi.onDidActivePanelChange`, the `WallEvent` stream, the `subscribeToActivity` store from `dormouse-lib/lib/terminal-registry`, and the `subscribeToMouseSelection` store from `dormouse-lib/lib/mouse-selection`. - **`tutorial-state.ts`** (`TutorialState`) — single in-memory progress store, persisted as a JSON array of completed item ids under the `mouseterm-tut-v3` localStorage key. - **`tut-items.ts`** — section + item definitions (titles, hints) shared by runner and detector. Item ids are stable; they are the localStorage key suffixes. @@ -105,9 +105,9 @@ While the Copy paste section is open, pressing `p` toggles the **Place To Paste* ## Theme Picker -Implemented in `mouseterm-lib/lib/themes` and `mouseterm-lib/components/ThemePicker`. +Implemented in `dormouse-lib/lib/themes` and `dormouse-lib/components/ThemePicker`. -Bundled themes are provided by `mouseterm-lib/lib/themes` and include only GitHub variants. Users can install additional themes from OpenVSX through the dropdown footer action. +Bundled themes are provided by `dormouse-lib/lib/themes` and include only GitHub variants. Users can install additional themes from OpenVSX through the dropdown footer action. The picker appears only on `/playground`, inside `SiteHeader`, labeled `Theme:`. The trigger opens a dropdown of bundled and installed themes. The dropdown footer is always `Install theme from OpenVSX`, which opens the theme store dialog. Installed theme rows include an `X` delete control; deletion requires browser confirmation before removing the theme from localStorage. If the active installed theme is deleted, the picker falls back to the first bundled theme and applies it immediately. diff --git a/docs/specs/vscode.md b/docs/specs/vscode.md index 1ce26b8d..ff626839 100644 --- a/docs/specs/vscode.md +++ b/docs/specs/vscode.md @@ -204,7 +204,7 @@ connect-src ${webview.cspSource}; ``` pnpm build:vscode = - 1. pnpm --filter mouseterm-lib build (TypeScript compile) + 1. pnpm --filter dormouse-lib build (TypeScript compile) 2. pnpm --filter dormouse build:frontend (Vite: lib -> vscode-ext/media/) 3. pnpm --filter dormouse build (esbuild: extension.ts + pty-host.js -> dist/, copy node-pty prebuilds -> dist/node-pty) diff --git a/lib/.storybook/main.ts b/lib/.storybook/main.ts index fc6b3a3b..1776a5af 100644 --- a/lib/.storybook/main.ts +++ b/lib/.storybook/main.ts @@ -18,7 +18,7 @@ const config: StorybookConfig = { '@tauri-apps/api/core': stub, '@tauri-apps/plugin-shell': stub, '@tauri-apps/plugin-updater': stub, - 'mouseterm-lib': path.resolve(here, '..', 'src'), + 'dormouse-lib': path.resolve(here, '..', 'src'), }; return config; }, diff --git a/lib/package.json b/lib/package.json index 83886588..c0b94091 100644 --- a/lib/package.json +++ b/lib/package.json @@ -1,5 +1,5 @@ { - "name": "mouseterm-lib", + "name": "dormouse-lib", "version": "0.9.1", "license": "FSL-1.1-MIT", "private": true, diff --git a/lib/tsconfig.app.json b/lib/tsconfig.app.json index 0ff1f497..fcf334ed 100644 --- a/lib/tsconfig.app.json +++ b/lib/tsconfig.app.json @@ -16,7 +16,7 @@ "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, "paths": { - "mouseterm-lib/*": ["./src/*"] + "dormouse-lib/*": ["./src/*"] } }, "include": ["src"], diff --git a/package.json b/package.json index af3e019d..3d9f193e 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,19 @@ { - "name": "mouseterm-root", + "name": "dormouse-root", "private": true, "license": "FSL-1.1-MIT", "scripts": { - "build": "pnpm run build:vscode && pnpm --filter mouseterm-website build", + "build": "pnpm run build:vscode && pnpm --filter dormouse-website build", "test": "pnpm -r run test", - "dev:lib": "pnpm --filter mouseterm-lib dev", - "dev:standalone": "pnpm --filter mouseterm-standalone tauri dev", - "dev:website": "pnpm --filter mouseterm-website dev", - "build:vscode": "pnpm --filter mouseterm-lib build && pnpm --filter dormouse build:frontend && pnpm --filter dormouse build", - "build:standalone": "pnpm --filter mouseterm-standalone tauri build", - "build:website": "pnpm --filter mouseterm-website build", + "dev:lib": "pnpm --filter dormouse-lib dev", + "dev:standalone": "pnpm --filter dormouse-standalone tauri dev", + "dev:website": "pnpm --filter dormouse-website dev", + "build:vscode": "pnpm --filter dormouse-lib build && pnpm --filter dormouse build:frontend && pnpm --filter dormouse build", + "build:standalone": "pnpm --filter dormouse-standalone tauri build", + "build:website": "pnpm --filter dormouse-website build", "dogfood:vscode": "pnpm run build:vscode && pnpm --filter dormouse dogfood", "dogfood:standalone": "bash standalone/scripts/dogfood.sh", - "storybook": "pnpm --filter mouseterm-lib storybook", + "storybook": "pnpm --filter dormouse-lib storybook", "bundle-themes": "node lib/scripts/bundle-themes.mjs" }, "pnpm": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e3ede9f5..f5acbc51 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -104,7 +104,7 @@ importers: dockview-react: specifier: ^5.1.0 version: 5.1.0(react@19.2.4) - mouseterm-lib: + dormouse-lib: specifier: workspace:* version: link:../lib react: @@ -196,7 +196,7 @@ importers: ascii-splash: specifier: 0.3.0 version: 0.3.0 - mouseterm-lib: + dormouse-lib: specifier: workspace:* version: link:../lib react: diff --git a/standalone/package.json b/standalone/package.json index 3c0b8a56..5f1731e1 100644 --- a/standalone/package.json +++ b/standalone/package.json @@ -1,5 +1,5 @@ { - "name": "mouseterm-standalone", + "name": "dormouse-standalone", "private": true, "version": "0.1.0", "license": "FSL-1.1-MIT", @@ -18,7 +18,7 @@ "@xterm/addon-fit": "^0.11.0", "@xterm/xterm": "^6.0.0", "dockview-react": "^5.1.0", - "mouseterm-lib": "workspace:*", + "dormouse-lib": "workspace:*", "react": "^19.0.0", "react-dom": "^19.0.0", "tailwind-variants": "^3.2.2" diff --git a/standalone/scripts/dogfood.sh b/standalone/scripts/dogfood.sh index bcff5a66..edf4a324 100755 --- a/standalone/scripts/dogfood.sh +++ b/standalone/scripts/dogfood.sh @@ -29,11 +29,11 @@ if [[ "${1:-}" == "--install" ]]; then case "$(uname -s)" in Darwin) BUNDLE_ARGS=(--bundles app) ;; esac - pnpm --filter mouseterm-standalone tauri build \ + pnpm --filter dormouse-standalone tauri build \ -c '{"bundle":{"createUpdaterArtifacts":false}}' "${BUNDLE_ARGS[@]}" else # Fast build: skip bundling entirely since we just need the exe - pnpm --filter mouseterm-standalone tauri build --no-bundle + pnpm --filter dormouse-standalone tauri build --no-bundle fi if [[ "${1:-}" == "--install" ]]; then diff --git a/standalone/sidecar/package.json b/standalone/sidecar/package.json index 90e9b1c3..08c81826 100644 --- a/standalone/sidecar/package.json +++ b/standalone/sidecar/package.json @@ -1,5 +1,5 @@ { - "name": "mouseterm-sidecar", + "name": "dormouse-sidecar", "private": true, "version": "0.1.0", "main": "main.js", diff --git a/standalone/src/main.tsx b/standalone/src/main.tsx index d39b8b78..29b423b1 100644 --- a/standalone/src/main.tsx +++ b/standalone/src/main.tsx @@ -1,12 +1,12 @@ import { StrictMode, useEffect, useState } from "react"; import { createRoot } from "react-dom/client"; import { invoke } from "@tauri-apps/api/core"; -import { setPlatform } from "mouseterm-lib/lib/platform"; -import { resumeOrRestore } from "mouseterm-lib/lib/reconnect"; -import { setDefaultShellOpts } from "mouseterm-lib/lib/shell-defaults"; -import { restoreActiveTheme } from "mouseterm-lib/lib/themes"; -import App from "mouseterm-lib/App"; -import "mouseterm-lib/index.css"; +import { setPlatform } from "dormouse-lib/lib/platform"; +import { resumeOrRestore } from "dormouse-lib/lib/reconnect"; +import { setDefaultShellOpts } from "dormouse-lib/lib/shell-defaults"; +import { restoreActiveTheme } from "dormouse-lib/lib/themes"; +import App from "dormouse-lib/App"; +import "dormouse-lib/index.css"; import { TauriAdapter } from "./tauri-adapter"; import { UpdateBanner } from "./UpdateBanner"; import { UpdateDebugDialog } from "./UpdateDebugDialog"; @@ -73,7 +73,7 @@ function ConnectedUpdateBanner() { // Await init() first to register event listeners before reconnecting async function bootstrap() { await platform.init(); - const { initAlertStateReceiver } = await import("mouseterm-lib/lib/terminal-registry"); + const { initAlertStateReceiver } = await import("dormouse-lib/lib/terminal-registry"); initAlertStateReceiver(); restoreActiveTheme(); diff --git a/standalone/src/tauri-adapter.ts b/standalone/src/tauri-adapter.ts index 5705620e..9c81583a 100644 --- a/standalone/src/tauri-adapter.ts +++ b/standalone/src/tauri-adapter.ts @@ -1,16 +1,16 @@ import { invoke as rawInvoke } from "@tauri-apps/api/core"; import { listen } from "@tauri-apps/api/event"; -import type { AlertStateDetail, PlatformAdapter, PtyInfo } from "mouseterm-lib/lib/platform/types"; -import { AlertManager, type SessionStatus } from "mouseterm-lib/lib/alert-manager"; +import type { AlertStateDetail, PlatformAdapter, PtyInfo } from "dormouse-lib/lib/platform/types"; +import { AlertManager, type SessionStatus } from "dormouse-lib/lib/alert-manager"; import { applyTerminalProtocolEvents, collectTerminalSemanticEvents, collectTerminalProtocolResponses, TerminalProtocolParser, -} from "mouseterm-lib/lib/terminal-protocol"; +} from "dormouse-lib/lib/terminal-protocol"; import { applyTerminalSemanticEventsByPtyId, -} from "mouseterm-lib/lib/terminal-state-store"; +} from "dormouse-lib/lib/terminal-state-store"; function invoke(cmd: string, args?: Record<string, unknown>): void { rawInvoke(cmd, args).catch((err) => diff --git a/standalone/src/updater.ts b/standalone/src/updater.ts index cb54144f..fc455038 100644 --- a/standalone/src/updater.ts +++ b/standalone/src/updater.ts @@ -4,7 +4,7 @@ import { getCurrentWindow } from '@tauri-apps/api/window'; import { getVersion } from '@tauri-apps/api/app'; import { open } from '@tauri-apps/plugin-shell'; import { invoke } from '@tauri-apps/api/core'; -import { PLATFORM_STRING } from 'mouseterm-lib/lib/platform'; +import { PLATFORM_STRING } from 'dormouse-lib/lib/platform'; import type { UpdateBannerState } from './UpdateBanner'; const GITHUB_REPO_URL = 'https://github.com/diffplug/mouseterm'; diff --git a/standalone/tsconfig.json b/standalone/tsconfig.json index 58028696..e1f66d18 100644 --- a/standalone/tsconfig.json +++ b/standalone/tsconfig.json @@ -16,7 +16,7 @@ "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, "paths": { - "mouseterm-lib/*": ["../lib/src/*"] + "dormouse-lib/*": ["../lib/src/*"] } }, "include": ["src"] diff --git a/standalone/vite.config.ts b/standalone/vite.config.ts index 5acbf70a..ab69ce86 100644 --- a/standalone/vite.config.ts +++ b/standalone/vite.config.ts @@ -13,7 +13,7 @@ export default defineConfig({ resolve: { dedupe: ["react", "react-dom"], alias: { - "mouseterm-lib": path.resolve(libDir, "src"), + "dormouse-lib": path.resolve(libDir, "src"), }, }, // Tauri expects a fixed port; fail if that port is not available diff --git a/standalone/vitest.config.ts b/standalone/vitest.config.ts index d91a0fe2..99621273 100644 --- a/standalone/vitest.config.ts +++ b/standalone/vitest.config.ts @@ -4,7 +4,7 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ resolve: { alias: { - 'mouseterm-lib': path.resolve(__dirname, '../lib/src'), + 'dormouse-lib': path.resolve(__dirname, '../lib/src'), }, }, test: { diff --git a/website/package.json b/website/package.json index d8704acc..f3081aef 100644 --- a/website/package.json +++ b/website/package.json @@ -1,5 +1,5 @@ { - "name": "mouseterm-website", + "name": "dormouse-website", "private": true, "version": "0.1.0", "license": "FSL-1.1-MIT", @@ -16,7 +16,7 @@ "dependencies": { "@phosphor-icons/react": "^2.1.10", "ascii-splash": "0.3.0", - "mouseterm-lib": "workspace:*", + "dormouse-lib": "workspace:*", "react": "^19.2.0", "react-dom": "^19.2.0", "react-router-dom": "^6.14.1" diff --git a/website/src/lib/ascii-splash-runner.test.ts b/website/src/lib/ascii-splash-runner.test.ts index 439d9e4a..7ae63756 100644 --- a/website/src/lib/ascii-splash-runner.test.ts +++ b/website/src/lib/ascii-splash-runner.test.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { FakePtyAdapter } from "mouseterm-lib/lib/platform/fake-adapter"; +import { FakePtyAdapter } from "dormouse-lib/lib/platform/fake-adapter"; import { AsciiSplashRunner } from "./ascii-splash-runner"; function createHarness(args: string[] = []) { diff --git a/website/src/lib/ascii-splash-runner.ts b/website/src/lib/ascii-splash-runner.ts index 9ce7f6b3..2e0fde56 100644 --- a/website/src/lib/ascii-splash-runner.ts +++ b/website/src/lib/ascii-splash-runner.ts @@ -60,8 +60,8 @@ import { LEAVE_ALT_SCREEN, MOUSE_DISABLE, MOUSE_ENABLE, -} from "mouseterm-lib/lib/ansi"; -import type { FakePtyAdapter } from "mouseterm-lib/lib/platform/fake-adapter"; +} from "dormouse-lib/lib/ansi"; +import type { FakePtyAdapter } from "dormouse-lib/lib/platform/fake-adapter"; import type { InteractiveProgram } from "./tutorial-shell"; type QualityPreset = "low" | "medium" | "high"; diff --git a/website/src/lib/changelog-runner.ts b/website/src/lib/changelog-runner.ts index daa15720..622b0488 100644 --- a/website/src/lib/changelog-runner.ts +++ b/website/src/lib/changelog-runner.ts @@ -10,8 +10,8 @@ import { MOUSE_ENABLE, RESET, fg, -} from "mouseterm-lib/lib/ansi"; -import type { FakePtyAdapter } from "mouseterm-lib/lib/platform/fake-adapter"; +} from "dormouse-lib/lib/ansi"; +import type { FakePtyAdapter } from "dormouse-lib/lib/platform/fake-adapter"; import type { InteractiveProgram } from "./tutorial-shell"; import changelogData from "../data/changelog.json"; diff --git a/website/src/lib/playground-shells.test.ts b/website/src/lib/playground-shells.test.ts index 61e691da..5a99aaf9 100644 --- a/website/src/lib/playground-shells.test.ts +++ b/website/src/lib/playground-shells.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it, vi } from "vitest"; -import { FakePtyAdapter } from "mouseterm-lib/lib/platform/fake-adapter"; +import { FakePtyAdapter } from "dormouse-lib/lib/platform/fake-adapter"; import { PlaygroundShellRegistry } from "./playground-shells"; import type { InteractiveProgram } from "./tutorial-shell"; diff --git a/website/src/lib/playground-shells.ts b/website/src/lib/playground-shells.ts index 6708b767..c39d5217 100644 --- a/website/src/lib/playground-shells.ts +++ b/website/src/lib/playground-shells.ts @@ -1,4 +1,4 @@ -import type { FakePtyAdapter } from "mouseterm-lib/lib/platform/fake-adapter"; +import type { FakePtyAdapter } from "dormouse-lib/lib/platform/fake-adapter"; import { TutorialShell, type InteractiveProgram } from "./tutorial-shell"; export type StartPlaygroundProgram = ( diff --git a/website/src/lib/tut-detector.test.ts b/website/src/lib/tut-detector.test.ts index 87be501b..9cdcc5a8 100644 --- a/website/src/lib/tut-detector.test.ts +++ b/website/src/lib/tut-detector.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from "vitest"; -import { DEFAULT_MOUSE_SELECTION_STATE, type MouseSelectionState } from "mouseterm-lib/lib/mouse-selection"; -import type { ActivityState } from "mouseterm-lib/lib/terminal-registry"; +import { DEFAULT_MOUSE_SELECTION_STATE, type MouseSelectionState } from "dormouse-lib/lib/mouse-selection"; +import type { ActivityState } from "dormouse-lib/lib/terminal-registry"; import { TutDetector } from "./tut-detector"; import { TutorialState } from "./tutorial-state"; diff --git a/website/src/lib/tut-detector.ts b/website/src/lib/tut-detector.ts index fbbc54e7..2ab87d93 100644 --- a/website/src/lib/tut-detector.ts +++ b/website/src/lib/tut-detector.ts @@ -1,4 +1,4 @@ -import { DEFAULT_MOUSE_SELECTION_STATE } from "mouseterm-lib/lib/mouse-selection"; +import { DEFAULT_MOUSE_SELECTION_STATE } from "dormouse-lib/lib/mouse-selection"; import type { TutorialState } from "./tutorial-state"; interface DockviewApi { @@ -7,10 +7,10 @@ interface DockviewApi { listener: (panel: { id?: string } | undefined) => void, ) => { dispose: () => void }; } -type WallEvent = import("mouseterm-lib/components/Wall").WallEvent; -type WallMode = import("mouseterm-lib/components/Wall").WallMode; -type ActivityState = import("mouseterm-lib/lib/terminal-registry").ActivityState; -type MouseSelectionState = import("mouseterm-lib/lib/mouse-selection").MouseSelectionState; +type WallEvent = import("dormouse-lib/components/Wall").WallEvent; +type WallMode = import("dormouse-lib/components/Wall").WallMode; +type ActivityState = import("dormouse-lib/lib/terminal-registry").ActivityState; +type MouseSelectionState = import("dormouse-lib/lib/mouse-selection").MouseSelectionState; interface ActivityStoreModule { subscribeToActivity: (listener: () => void) => () => void; diff --git a/website/src/lib/tut-items.ts b/website/src/lib/tut-items.ts index 8931f75f..4d060433 100644 --- a/website/src/lib/tut-items.ts +++ b/website/src/lib/tut-items.ts @@ -1,4 +1,4 @@ -import { cfg } from "mouseterm-lib/cfg"; +import { cfg } from "dormouse-lib/cfg"; const USER_ATTENTION_SECS = Math.round(cfg.alert.userAttention / 1000); diff --git a/website/src/lib/tut-runner.test.ts b/website/src/lib/tut-runner.test.ts index 60041939..2a4178ca 100644 --- a/website/src/lib/tut-runner.test.ts +++ b/website/src/lib/tut-runner.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { FakePtyAdapter } from "mouseterm-lib/lib/platform/fake-adapter"; +import { FakePtyAdapter } from "dormouse-lib/lib/platform/fake-adapter"; import { SECTIONS, type ItemId } from "./tut-items"; import { TutRunner } from "./tut-runner"; import { TutorialState } from "./tutorial-state"; diff --git a/website/src/lib/tut-runner.ts b/website/src/lib/tut-runner.ts index 4fa081df..e228fd1b 100644 --- a/website/src/lib/tut-runner.ts +++ b/website/src/lib/tut-runner.ts @@ -9,9 +9,9 @@ import { LEAVE_ALT_SCREEN, RESET, fg, -} from "mouseterm-lib/lib/ansi"; -import { cfg } from "mouseterm-lib/cfg"; -import type { FakePtyAdapter } from "mouseterm-lib/lib/platform/fake-adapter"; +} from "dormouse-lib/lib/ansi"; +import { cfg } from "dormouse-lib/cfg"; +import type { FakePtyAdapter } from "dormouse-lib/lib/platform/fake-adapter"; import type { InteractiveProgram } from "./tutorial-shell"; import { SECTIONS, type Item } from "./tut-items"; import type { TutorialState } from "./tutorial-state"; diff --git a/website/src/lib/tutorial-shell.ts b/website/src/lib/tutorial-shell.ts index c596385b..8caa1fec 100644 --- a/website/src/lib/tutorial-shell.ts +++ b/website/src/lib/tutorial-shell.ts @@ -1,4 +1,4 @@ -import { CLEAR_LINE, PROMPT, RESET, fg } from 'mouseterm-lib/lib/ansi'; +import { CLEAR_LINE, PROMPT, RESET, fg } from 'dormouse-lib/lib/ansi'; export type SendOutput = (data: string) => void; diff --git a/website/src/pages/Home.tsx b/website/src/pages/Home.tsx index f48e527f..f76cb056 100644 --- a/website/src/pages/Home.tsx +++ b/website/src/pages/Home.tsx @@ -20,7 +20,7 @@ import visualStudioIconUrl from "../assets/visual-studio-icon.svg"; import tinyIconUrl from "../assets/icon-tiny-dark.png"; import phoneMockupUrl from "../assets/phone-mockup.webp"; import standaloneLatest from "@standalone-latest"; -import { prefersReducedMotion } from "mouseterm-lib/lib/ui-geometry"; +import { prefersReducedMotion } from "dormouse-lib/lib/ui-geometry"; import { NotifySignupForm } from "../components/NotifySignupForm"; export { Home as Component }; diff --git a/website/src/pages/Playground.tsx b/website/src/pages/Playground.tsx index 9c83dfdb..a00d4dfb 100644 --- a/website/src/pages/Playground.tsx +++ b/website/src/pages/Playground.tsx @@ -1,7 +1,7 @@ import { useState, useEffect, useCallback, useRef } from "react"; import SiteHeader from "../components/SiteHeader"; import { PlaceToPaste } from "../components/PlaceToPaste"; -import { ThemePicker } from "mouseterm-lib/components/ThemePicker"; +import { ThemePicker } from "dormouse-lib/components/ThemePicker"; import { PlaygroundShellRegistry } from "../lib/playground-shells"; import { TutorialState } from "../lib/tutorial-state"; import { TutDetector } from "../lib/tut-detector"; @@ -14,8 +14,8 @@ const PANE_MAIN = "tut-main"; const PANE_BOXED = "tut-boxed"; const PANE_SPLASH = "tut-splash"; -type FakePtyAdapter = import("mouseterm-lib/lib/platform/fake-adapter").FakePtyAdapter; -type WallEvent = import("mouseterm-lib/components/Wall").WallEvent; +type FakePtyAdapter = import("dormouse-lib/lib/platform/fake-adapter").FakePtyAdapter; +type WallEvent = import("dormouse-lib/components/Wall").WallEvent; type DockviewDisposable = { dispose: () => void }; // Tailwind's md breakpoint — matches the header's `md:top-20` so the pane @@ -69,13 +69,13 @@ function Playground() { { id: PANE_SPLASH, command: "ascii-splash" }, ]; async function loadWall() { - const platform = await import("mouseterm-lib/lib/platform"); - const registry = await import("mouseterm-lib/lib/terminal-registry"); - const mouseSelection = await import("mouseterm-lib/lib/mouse-selection"); - const wall = await import("mouseterm-lib/components/Wall"); - const scenarios = await import("mouseterm-lib/lib/platform/fake-scenarios"); + const platform = await import("dormouse-lib/lib/platform"); + const registry = await import("dormouse-lib/lib/terminal-registry"); + const mouseSelection = await import("dormouse-lib/lib/mouse-selection"); + const wall = await import("dormouse-lib/components/Wall"); + const scenarios = await import("dormouse-lib/lib/platform/fake-scenarios"); const asciiSplash = await import("../lib/ascii-splash-runner"); - await import("mouseterm-lib/index.css"); + await import("dormouse-lib/index.css"); if (cancelled) return; const adapter = platform.initPlatform("fake"); diff --git a/website/src/pages/Pocket.tsx b/website/src/pages/Pocket.tsx index 80e58229..edfb3178 100644 --- a/website/src/pages/Pocket.tsx +++ b/website/src/pages/Pocket.tsx @@ -2,15 +2,15 @@ import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from " import { ShareIcon } from "@phosphor-icons/react"; import SiteHeader, { STATIC_PAGE_HEADER_STYLE } from "../components/SiteHeader"; import { NotifySignupForm } from "../components/NotifySignupForm"; -import { MobileTerminalUi, type MobileTerminalKeyboardMode, type MobileTerminalTouchMode } from "mouseterm-lib/components/MobileTerminalUi"; -import { MobileWall, useMobileWallSessionItems, type MobileWallSession } from "mouseterm-lib/components/MobileWall"; -import { ThemePicker } from "mouseterm-lib/components/ThemePicker"; -import { restoreActiveTheme } from "mouseterm-lib/lib/themes"; +import { MobileTerminalUi, type MobileTerminalKeyboardMode, type MobileTerminalTouchMode } from "dormouse-lib/components/MobileTerminalUi"; +import { MobileWall, useMobileWallSessionItems, type MobileWallSession } from "dormouse-lib/components/MobileWall"; +import { ThemePicker } from "dormouse-lib/components/ThemePicker"; +import { restoreActiveTheme } from "dormouse-lib/lib/themes"; import { getMouseSelectionSnapshot, setOverride as setMouseOverride, subscribeToMouseSelection, -} from "mouseterm-lib/lib/mouse-selection"; +} from "dormouse-lib/lib/mouse-selection"; import { PlaygroundShellRegistry } from "../lib/playground-shells"; import { TutorialState } from "../lib/tutorial-state"; import { BUSY_DEMO_DURATION_MS, BUSY_DEMO_INTERVAL_MS, TutRunner } from "../lib/tut-runner"; @@ -18,7 +18,7 @@ import { ChangelogRunner } from "../lib/changelog-runner"; export { Pocket as Component }; -type FakePtyAdapter = import("mouseterm-lib/lib/platform/fake-adapter").FakePtyAdapter; +type FakePtyAdapter = import("dormouse-lib/lib/platform/fake-adapter").FakePtyAdapter; const POCKET_PANE = "pocket-ascii-splash"; const POCKET_THEME_ID = "vscode.theme-kimbie-dark.kimbie-dark"; @@ -86,11 +86,11 @@ function PocketTerminalExperience({ let cancelled = false; async function loadWall() { - const platform = await import("mouseterm-lib/lib/platform"); - const registry = await import("mouseterm-lib/lib/terminal-registry"); - const scenarios = await import("mouseterm-lib/lib/platform/fake-scenarios"); + const platform = await import("dormouse-lib/lib/platform"); + const registry = await import("dormouse-lib/lib/terminal-registry"); + const scenarios = await import("dormouse-lib/lib/platform/fake-scenarios"); const asciiSplash = await import("../lib/ascii-splash-runner"); - await import("mouseterm-lib/index.css"); + await import("dormouse-lib/index.css"); if (cancelled) return; const adapter = platform.initPlatform("fake"); @@ -195,7 +195,7 @@ function PocketTerminalExperience({ onSessionSelect={setActivePaneId} onSendInput={(data) => adapterRef.current?.writePty(activePaneId, data)} onPaste={async () => { - const { doPaste } = await import("mouseterm-lib/lib/clipboard"); + const { doPaste } = await import("dormouse-lib/lib/clipboard"); await doPaste(activePaneId); }} /> diff --git a/website/tsconfig.json b/website/tsconfig.json index 0d3e696c..42a9f690 100644 --- a/website/tsconfig.json +++ b/website/tsconfig.json @@ -14,7 +14,7 @@ "baseUrl": ".", "paths": { "ascii-splash-internal/*": ["node_modules/ascii-splash/dist/*"], - "mouseterm-lib/*": ["../lib/src/*"] + "dormouse-lib/*": ["../lib/src/*"] }, "strict": true, "noUnusedLocals": true, diff --git a/website/vite.config.ts b/website/vite.config.ts index ea31e169..a815f43b 100644 --- a/website/vite.config.ts +++ b/website/vite.config.ts @@ -7,7 +7,7 @@ export default defineConfig({ plugins: [react(), tailwindcss()], resolve: { alias: { - "mouseterm-lib": path.resolve(__dirname, "../lib/src"), + "dormouse-lib": path.resolve(__dirname, "../lib/src"), "ascii-splash-internal": path.resolve( __dirname, "node_modules/ascii-splash/dist", From 35b57d7ec6b4ea26abf617e0d6835037485d733b Mon Sep 17 00:00:00 2001 From: Ned Twigg <ned.twigg@diffplug.com> Date: Sun, 17 May 2026 21:50:10 -0700 Subject: [PATCH 10/24] Change the share image. --- website/public/og-image.jpg | Bin 76478 -> 83787 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/website/public/og-image.jpg b/website/public/og-image.jpg index af0986434a688ec12152bb38ca024ed668d2dc99..edbc04cceb2739b28be7768f5f1b1fab06df660e 100644 GIT binary patch literal 83787 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 -xE%_}-d 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%0ABrzO3!Z?IW%#ZAlAn)48x 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 literal 76478 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�JA+!cd#BH;+|LISuNxo6T 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 YJ$|$VlfwBz@<<~Tn4R8*GW<9HKk8n3DgXcg From e4df71036c491a1793d62aa9bc7bc11f0a08858b Mon Sep 17 00:00:00 2001 From: Ned Twigg <ned.twigg@diffplug.com> Date: Sun, 17 May 2026 21:56:21 -0700 Subject: [PATCH 11/24] Rename repo to diffplug/dormouse --- .claude/commands/release-notes.md | 4 ++-- CHANGELOG.md | 20 +++++++++---------- docs/specs/deploy.md | 12 +++++------ .../wall/use-session-persistence.ts | 2 +- lib/src/lib/platform/types.ts | 2 +- scripts/sign-and-deploy.sh | 2 +- standalone/src-tauri/src/lib.rs | 2 +- standalone/src/tauri-adapter.ts | 2 +- standalone/src/updater.ts | 2 +- vscode-ext/README.md | 2 +- vscode-ext/package.json | 2 +- website/public/standalone-latest.json | 8 ++++---- website/src/components/SiteHeader.tsx | 2 +- website/src/pages/Changelog.tsx | 2 +- website/src/pages/Home.tsx | 4 ++-- 15 files changed, 34 insertions(+), 34 deletions(-) diff --git a/.claude/commands/release-notes.md b/.claude/commands/release-notes.md index 7f2a22d9..2aa6524d 100644 --- a/.claude/commands/release-notes.md +++ b/.claude/commands/release-notes.md @@ -50,7 +50,7 @@ _Recommended bump: **<breaking|added|bugfix>** — <one-sentence justification n ### Added - Summary that affects both artifacts (no leading emoji) ... - 🔌 VS Code-only summary ... -- 🖥️ Standalone-only user-facing summary ([#123](https://github.com/diffplug/mouseterm/pull/123)). +- 🖥️ Standalone-only user-facing summary ([#123](https://github.com/diffplug/dormouse/pull/123)). ### Changed - ... @@ -63,7 +63,7 @@ Rules for the entries: - One line per PR, written in user-facing terms (not "refactored X" — say what the user sees) - Lead each entry with the artifact emoji from the header at the top of `CHANGELOG.md`: 🖥️ for standalone-only, 🔌 for VS Code plugin-only, no emoji for changes that ship in both. Decide based on whether the user-visible behavior actually surfaces in each artifact — a PR that touches `lib/` is *both* only if both artifacts consume that code path; otherwise it's whichever one ships it. - Within each of Added / Changed / Fixed, sort entries by artifact: items that affect both (no emoji) first, then VS Code-only (🔌), then standalone-only (🖥️). -- Link the PR using `https://github.com/diffplug/mouseterm/pull/<N>`. For direct-push commits with no PR, link the commit instead: `https://github.com/diffplug/mouseterm/commit/<sha>` +- Link the PR using `https://github.com/diffplug/dormouse/pull/<N>`. For direct-push commits with no PR, link the commit instead: `https://github.com/diffplug/dormouse/commit/<sha>` - Omit any of Added / Changed / Fixed if it would be empty - Use today's date (`YYYY-MM-DD`) and the recommended `X.Y.Z` diff --git a/CHANGELOG.md b/CHANGELOG.md index 940f14a1..c748a14e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,24 +10,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/). Release ## [0.9.1] - 2026-05-01 ### Changed -- 🖥️ Drop-to-paste from the OS file explorer is temporarily inert on standalone while we wait on upstream Tauri ([tauri#14373](https://github.com/tauri-apps/tauri/issues/14373)) to allow native drag-drop without blocking HTML5 drag events ([#39](https://github.com/diffplug/mouseterm/pull/39)). +- 🖥️ Drop-to-paste from the OS file explorer is temporarily inert on standalone while we wait on upstream Tauri ([tauri#14373](https://github.com/tauri-apps/tauri/issues/14373)) to allow native drag-drop without blocking HTML5 drag events ([#39](https://github.com/diffplug/dormouse/pull/39)). ### Fixed -- The mouse-override banner now renders inline in the terminal pane body and no longer stacks with the action-button tooltip ([#43](https://github.com/diffplug/mouseterm/pull/43)). -- Themes with translucent selection backgrounds (e.g. Selenized Dark) no longer bleed through MouseTerm's solid AppBar and tab fills ([#37](https://github.com/diffplug/mouseterm/pull/37)). -- 🖥️ Force-closing the standalone host now reliably kills the Node sidecar tree via a Windows Job Object / Unix process group, so subsequent builds no longer hit orphan `node.exe` processes locking files ([#41](https://github.com/diffplug/mouseterm/pull/41)). -- 🖥️ Standalone macOS terminals run zsh as a login shell when no args are provided, so `~/.zprofile` runs and Homebrew/asdf land on `PATH` ([#40](https://github.com/diffplug/mouseterm/pull/40)). -- 🖥️ Pane drag-and-drop reordering works again on standalone ([#39](https://github.com/diffplug/mouseterm/pull/39)). +- The mouse-override banner now renders inline in the terminal pane body and no longer stacks with the action-button tooltip ([#43](https://github.com/diffplug/dormouse/pull/43)). +- Themes with translucent selection backgrounds (e.g. Selenized Dark) no longer bleed through MouseTerm's solid AppBar and tab fills ([#37](https://github.com/diffplug/dormouse/pull/37)). +- 🖥️ Force-closing the standalone host now reliably kills the Node sidecar tree via a Windows Job Object / Unix process group, so subsequent builds no longer hit orphan `node.exe` processes locking files ([#41](https://github.com/diffplug/dormouse/pull/41)). +- 🖥️ Standalone macOS terminals run zsh as a login shell when no args are provided, so `~/.zprofile` runs and Homebrew/asdf land on `PATH` ([#40](https://github.com/diffplug/dormouse/pull/40)). +- 🖥️ Pane drag-and-drop reordering works again on standalone ([#39](https://github.com/diffplug/dormouse/pull/39)). ## [0.9.0] - 2026-04-30 ### Added -- 🖥️ Debug dialog for failed auto-updates — surfaces the error and copies a pre-filled bug report (version, platform, last ~10 KB of `mouseterm.log`) ([#35](https://github.com/diffplug/mouseterm/pull/35)). +- 🖥️ Debug dialog for failed auto-updates — surfaces the error and copies a pre-filled bug report (version, platform, last ~10 KB of `mouseterm.log`) ([#35](https://github.com/diffplug/dormouse/pull/35)). ### Fixed -- Terminals auto-spawned from a blank workspace now respect the selected shell ([#33](https://github.com/diffplug/mouseterm/pull/33)). -- 🖥️ Polish app bar header to align with pane chrome and shared design tokens ([#34](https://github.com/diffplug/mouseterm/pull/34)). -- 🖥️ macOS auto-update — strip AppleDouble (`._*`) sidecars from the signed tarball that were breaking every v0.7.x → v0.8.0 install ([#35](https://github.com/diffplug/mouseterm/pull/35)). +- Terminals auto-spawned from a blank workspace now respect the selected shell ([#33](https://github.com/diffplug/dormouse/pull/33)). +- 🖥️ Polish app bar header to align with pane chrome and shared design tokens ([#34](https://github.com/diffplug/dormouse/pull/34)). +- 🖥️ macOS auto-update — strip AppleDouble (`._*`) sidecars from the signed tarball that were breaking every v0.7.x → v0.8.0 install ([#35](https://github.com/diffplug/dormouse/pull/35)). ## [0.8.0] - 2026-04-29 - Add intuitive shortcuts alongside the tmux shortcuts. diff --git a/docs/specs/deploy.md b/docs/specs/deploy.md index 52c11953..8e00036a 100644 --- a/docs/specs/deploy.md +++ b/docs/specs/deploy.md @@ -153,9 +153,9 @@ All release assets use **stable filenames** (no version in the name). This allow The dormouse.sh download page can link directly to the latest release with no server-side logic: ``` -https://github.com/diffplug/mouseterm/releases/latest/download/Dormouse-windows-x64-setup.exe -https://github.com/diffplug/mouseterm/releases/latest/download/Dormouse-macos-aarch64.tar.gz -https://github.com/diffplug/mouseterm/releases/latest/download/Dormouse-linux-x86_64.AppImage +https://github.com/diffplug/dormouse/releases/latest/download/Dormouse-windows-x64-setup.exe +https://github.com/diffplug/dormouse/releases/latest/download/Dormouse-macos-aarch64.tar.gz +https://github.com/diffplug/dormouse/releases/latest/download/Dormouse-linux-x86_64.AppImage ``` These can later be migrated to `dormouse.sh/download/...` URLs backed by Cloudflare R2 (for analytics) without changing anything in the app — only the website links and the updater endpoint URL in `tauri.conf.json` would change. @@ -181,15 +181,15 @@ Generated by the local script after signing. The script writes it to `website/pu "pub_date": "2026-03-25T12:00:00Z", "platforms": { "windows-x86_64": { - "url": "https://github.com/diffplug/mouseterm/releases/download/v0.1.0/Dormouse-windows-x64-setup.exe", + "url": "https://github.com/diffplug/dormouse/releases/download/v0.1.0/Dormouse-windows-x64-setup.exe", "signature": "<contents of .sig file>" }, "darwin-aarch64": { - "url": "https://github.com/diffplug/mouseterm/releases/download/v0.1.0/Dormouse-macos-aarch64.tar.gz", + "url": "https://github.com/diffplug/dormouse/releases/download/v0.1.0/Dormouse-macos-aarch64.tar.gz", "signature": "<contents of .sig file>" }, "linux-x86_64": { - "url": "https://github.com/diffplug/mouseterm/releases/download/v0.1.0/Dormouse-linux-x86_64.AppImage", + "url": "https://github.com/diffplug/dormouse/releases/download/v0.1.0/Dormouse-linux-x86_64.AppImage", "signature": "<contents of .sig file>" } } diff --git a/lib/src/components/wall/use-session-persistence.ts b/lib/src/components/wall/use-session-persistence.ts index 05a36870..0c814ae7 100644 --- a/lib/src/components/wall/use-session-persistence.ts +++ b/lib/src/components/wall/use-session-persistence.ts @@ -98,7 +98,7 @@ export function useSessionPersistence({ platform.onRequestSessionFlush(handleSessionFlushRequest); window.addEventListener('pagehide', handlePageHide); - // Inert in Tauri standalone today; see diffplug/mouseterm#38 and tauri-apps/tauri#14373. + // Inert in Tauri standalone today; see diffplug/dormouse#38 and tauri-apps/tauri#14373. const unsubFilesDropped = platform.onFilesDropped?.((paths) => { if (paths.length === 0) return; const sid = selectedTypeRef.current === 'pane' ? selectedIdRef.current : null; diff --git a/lib/src/lib/platform/types.ts b/lib/src/lib/platform/types.ts index 0568846e..7b41c58b 100644 --- a/lib/src/lib/platform/types.ts +++ b/lib/src/lib/platform/types.ts @@ -33,7 +33,7 @@ export interface PlatformAdapter { // instead of navigator.clipboard.readText() so adapters whose webview pops // a "Paste from <App>" confirmation (notably Tauri's WKWebView) can bypass it. readClipboardText?(): Promise<string | null>; - // Only present on adapters with a native (non-DOM) drag-drop source. Currently inert in Tauri; see diffplug/mouseterm#38 and tauri-apps/tauri#14373. + // Only present on adapters with a native (non-DOM) drag-drop source. Currently inert in Tauri; see diffplug/dormouse#38 and tauri-apps/tauri#14373. onFilesDropped?(handler: (paths: string[]) => void): () => void; // PTY event listeners diff --git a/scripts/sign-and-deploy.sh b/scripts/sign-and-deploy.sh index 5d2fb194..f85a9674 100755 --- a/scripts/sign-and-deploy.sh +++ b/scripts/sign-and-deploy.sh @@ -44,7 +44,7 @@ JSIGN_ALIAS="AUTHENTICATION" TSA_URL="http://ts.ssl.com" # GitHub repo -GITHUB_REPO="diffplug/mouseterm" +GITHUB_REPO="diffplug/dormouse" # Stable filenames for release assets (update bundles only) FNAME_WIN="Dormouse-windows-x64-setup.exe" diff --git a/standalone/src-tauri/src/lib.rs b/standalone/src-tauri/src/lib.rs index 721b020c..582fc5b1 100644 --- a/standalone/src-tauri/src/lib.rs +++ b/standalone/src-tauri/src/lib.rs @@ -599,7 +599,7 @@ pub fn run() { let refs: Vec<&dyn tauri::menu::IsMenuItem<_>> = items.iter().map(|b| b.as_ref()).collect(); Menu::with_items(handle, &refs) }) - // Inert while tauri.conf.json sets dragDropEnabled=false (needed for HTML5 pane drag). See diffplug/mouseterm#38 and tauri-apps/tauri#14373. + // Inert while tauri.conf.json sets dragDropEnabled=false (needed for HTML5 pane drag). See diffplug/dormouse#38 and tauri-apps/tauri#14373. .on_window_event(|window, event| { if let WindowEvent::DragDrop(DragDropEvent::Drop { paths, .. }) = event { let payload: Vec<String> = paths diff --git a/standalone/src/tauri-adapter.ts b/standalone/src/tauri-adapter.ts index 9c81583a..b5f89160 100644 --- a/standalone/src/tauri-adapter.ts +++ b/standalone/src/tauri-adapter.ts @@ -105,7 +105,7 @@ export class TauriAdapter implements PlatformAdapter { }), ); - // Inert while dragDropEnabled=false in tauri.conf.json. See diffplug/mouseterm#38 and tauri-apps/tauri#14373. + // Inert while dragDropEnabled=false in tauri.conf.json. See diffplug/dormouse#38 and tauri-apps/tauri#14373. this.unlistenFns.push( await listen<{ paths: string[] }>("dormouse://files-dropped", (event) => { const paths = event.payload.paths ?? []; diff --git a/standalone/src/updater.ts b/standalone/src/updater.ts index fc455038..aaef0a15 100644 --- a/standalone/src/updater.ts +++ b/standalone/src/updater.ts @@ -7,7 +7,7 @@ import { invoke } from '@tauri-apps/api/core'; import { PLATFORM_STRING } from 'dormouse-lib/lib/platform'; import type { UpdateBannerState } from './UpdateBanner'; -const GITHUB_REPO_URL = 'https://github.com/diffplug/mouseterm'; +const GITHUB_REPO_URL = 'https://github.com/diffplug/dormouse'; function openUrl(url: string, context: string): void { open(url).catch((e) => console.error(`[updater] Failed to open ${context}:`, e)); diff --git a/vscode-ext/README.md b/vscode-ext/README.md index 86b8ccc2..d0547bca 100644 --- a/vscode-ext/README.md +++ b/vscode-ext/README.md @@ -81,5 +81,5 @@ TODO: GIF showing Dormouse in various areas - Prefer a standalone terminal app? Self-updating installers available for Win, Mac and Linux at [dormouse.sh](https://dormouse.sh/#download) - You can try it in a [browser playground](https://dormouse.sh/playground) -- [GitHub](https://github.com/diffplug/mouseterm) +- [GitHub](https://github.com/diffplug/dormouse) - Brought to you by [DiffPlug](https://www.diffplug.com/) diff --git a/vscode-ext/package.json b/vscode-ext/package.json index b313f6a6..8f452555 100644 --- a/vscode-ext/package.json +++ b/vscode-ext/package.json @@ -9,7 +9,7 @@ "homepage": "https://dormouse.sh/", "repository": { "type": "git", - "url": "https://github.com/diffplug/mouseterm" + "url": "https://github.com/diffplug/dormouse" }, "engines": { "vscode": "^1.85.0" diff --git a/website/public/standalone-latest.json b/website/public/standalone-latest.json index 95c3863b..765b9916 100644 --- a/website/public/standalone-latest.json +++ b/website/public/standalone-latest.json @@ -1,18 +1,18 @@ { "version": "0.9.1", - "notes": "See https://github.com/diffplug/mouseterm/releases/tag/v0.9.1", + "notes": "See https://github.com/diffplug/dormouse/releases/tag/v0.9.1", "pub_date": "2026-05-01T22:02:25Z", "platforms": { "darwin-aarch64": { - "url": "https://github.com/diffplug/mouseterm/releases/download/v0.9.1/MouseTerm-macos-aarch64.tar.gz", + "url": "https://github.com/diffplug/dormouse/releases/download/v0.9.1/MouseTerm-macos-aarch64.tar.gz", "signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUlVUYlpCcFVqWDVhckMvSXc5aXd0TXNFdEUwcmVxOUpvZG53SmdZQ0ZhYlhDcUQ2WU1LWlF1eGx2bG15NHJEa3phK1ZVcjFIaWVGNGUwS3BqKzFXMmNPUWlmem1wMUJBSGdNPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNzc3NjcyOTQ0CWZpbGU6TW91c2VUZXJtLW1hY29zLWFhcmNoNjQudGFyLmd6ClE3OWlndURTdkI5Mm9FeXM0U1Q4RGxYbFFtU2xIN1RsN095VmhlZTJqQ215U01yRFphNE5jYnNvcTZjUGI5Q0dObHgyMTJieE1pK2ZwdXJFTFZMb0JBPT0K" }, "windows-x86_64": { - "url": "https://github.com/diffplug/mouseterm/releases/download/v0.9.1/MouseTerm-windows-x64-setup.exe", + "url": "https://github.com/diffplug/dormouse/releases/download/v0.9.1/MouseTerm-windows-x64-setup.exe", "signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUlVUYlpCcFVqWDVhckdQRmYzS1dnRnYzUzBiUUMzanNINFpzLzZTemtwNTM4L3NmOUN0SGxVcEZFcG5yOGxOS1JGRzY1Skw0RGRXenlQMHlmNlB6ZkRLYTNLeE9hWVB0U3dZPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNzc3NjcyOTQ0CWZpbGU6TW91c2VUZXJtLXdpbmRvd3MteDY0LXNldHVwLmV4ZQp5ZU9XbGlSNFFNTVd0NFdZWi9YQW9KU1pxZWtDS3gyYWY4OUJ4eHZ4YkN0UkNicVZ5OXBFakdWWktOcmd6R29LZHRMeHlReCswa3d5VTgvbTNEK0xBQT09Cg==" }, "linux-x86_64": { - "url": "https://github.com/diffplug/mouseterm/releases/download/v0.9.1/MouseTerm-linux-x86_64.AppImage", + "url": "https://github.com/diffplug/dormouse/releases/download/v0.9.1/MouseTerm-linux-x86_64.AppImage", "signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUlVUYlpCcFVqWDVhckdvR3lCT3ZHQW51b1gxUWVVbGNNVlFMK0ZDNmdKdE5zYURXaHliaDFodHdoOFJabHB4UjVsalB1QWpTQ2NCNmlqOER5bGRpdXlCY0p2VHRCZEVWK3dBPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNzc3NjcyOTQ1CWZpbGU6TW91c2VUZXJtLWxpbnV4LXg4Nl82NC5BcHBJbWFnZQoza240b1AyVWw2T3VKUmhFUXdRanRlNVVaVGRvUUUxL1VqZ3FCTmJrQmpzNmxPVnRpd3BxRzJwN3NyRnB1YmZoUUIxK2E3WG9ZbmlJUnFYK0J6Uk1EZz09Cg==" } } diff --git a/website/src/components/SiteHeader.tsx b/website/src/components/SiteHeader.tsx index 03de70de..80945a73 100644 --- a/website/src/components/SiteHeader.tsx +++ b/website/src/components/SiteHeader.tsx @@ -8,7 +8,7 @@ export const STATIC_PAGE_HEADER_STYLE: React.CSSProperties = { const NAV_LINKS: readonly { href: string; label: string; external?: boolean; hideOnMobile?: boolean }[] = [ { href: "/playground", label: "Playground", hideOnMobile: true }, { href: "/#download", label: "Download", hideOnMobile: true }, - { href: "https://github.com/diffplug/mouseterm", label: "GitHub", external: true }, + { href: "https://github.com/diffplug/dormouse", label: "GitHub", external: true }, ]; const CHROME_INACTIVE_BG = "var(--color-header-inactive-bg)"; diff --git a/website/src/pages/Changelog.tsx b/website/src/pages/Changelog.tsx index a56a93c2..91b1d408 100644 --- a/website/src/pages/Changelog.tsx +++ b/website/src/pages/Changelog.tsx @@ -131,7 +131,7 @@ function ReleaseArticle({ release }: { release: ChangelogRelease }) { ) : null} </div> <a - href={`https://github.com/diffplug/mouseterm/releases/tag/${release.tag}`} + href={`https://github.com/diffplug/dormouse/releases/tag/${release.tag}`} className="text-sm text-[var(--color-caramel)] hover:underline" target="_blank" rel="noopener noreferrer" diff --git a/website/src/pages/Home.tsx b/website/src/pages/Home.tsx index f76cb056..b02bdb31 100644 --- a/website/src/pages/Home.tsx +++ b/website/src/pages/Home.tsx @@ -852,7 +852,7 @@ function Home() { {INSTALL_STEPS["linux-x86_64"].pill} </DownloadButton> <DownloadButton - href="https://github.com/diffplug/mouseterm/issues/8" + href="https://github.com/diffplug/dormouse/issues/8" icon={<DotsThreeOutlineIcon size={22} weight="fill" />} peek="other" variant="compact" @@ -907,7 +907,7 @@ function Home() { <footer className="border-t border-[var(--color-text)]/20 py-10"> <div className="mx-auto max-w-5xl px-4 md:px-6 flex flex-wrap items-center justify-center gap-x-6 gap-y-2 text-base text-center opacity-50"> <a href="/dependencies" className="underline hover:opacity-100">Dependencies</a> - <a href="https://github.com/diffplug/mouseterm/issues" className="underline hover:opacity-100">Report an issue</a> + <a href="https://github.com/diffplug/dormouse/issues" className="underline hover:opacity-100">Report an issue</a> <p> Built by{" "} <a href="https://nedshed.dev" className="underline hover:opacity-100">nedshed.dev</a>{" "} From 26f7542448cad8246414542beea5e98325718623 Mon Sep 17 00:00:00 2001 From: Ned Twigg <ned.twigg@diffplug.com> Date: Sun, 17 May 2026 22:00:39 -0700 Subject: [PATCH 12/24] Remaining find-replace of `mouseterm` -> `dormouse` (except changelog) --- .claude/commands/release-notes.md | 6 ++--- .vscode/launch.json | 2 +- .vscode/tasks.json | 2 +- docs/specs/tutorial.md | 6 ++--- docs/specs/vscode.md | 28 ++++++++++++------------ lib/src/components/ThemeDebugger.tsx | 2 +- lib/src/components/Wall.tsx | 2 +- lib/src/lib/terminal-state.test.ts | 20 ++++++++--------- lib/src/lib/terminal-theme.ts | 2 +- lib/src/lib/themes/diagnostics.ts | 6 ++--- lib/src/stories/ShellCwd.stories.tsx | 4 ++-- scripts/patch-nsis-paths.pl | 2 +- standalone/sidecar/clipboard-ops.js | 2 +- standalone/sidecar/clipboard-ops.test.js | 10 ++++----- standalone/src-tauri/Cargo.lock | 2 +- website/scripts/generate-deps.js | 6 ++--- 16 files changed, 51 insertions(+), 51 deletions(-) diff --git a/.claude/commands/release-notes.md b/.claude/commands/release-notes.md index 2aa6524d..d6977763 100644 --- a/.claude/commands/release-notes.md +++ b/.claude/commands/release-notes.md @@ -1,10 +1,10 @@ --- name: release-notes -description: Draft release notes, recommend and apply a version bump, and update CHANGELOG.md for the next mouseterm release by analyzing all merge commits and squash-merged PRs since the last release tag. Used as step 2 of the release checklist in docs/specs/deploy.md. +description: Draft release notes, recommend and apply a version bump, and update CHANGELOG.md for the next dormouse release by analyzing all merge commits and squash-merged PRs since the last release tag. Used as step 2 of the release checklist in docs/specs/deploy.md. user-invocable: true --- -You are drafting release notes, recommending and applying a version bump, and updating CHANGELOG.md for the next mouseterm release. +You are drafting release notes, recommending and applying a version bump, and updating CHANGELOG.md for the next dormouse release. ## 1. Gather context @@ -26,7 +26,7 @@ Also read the current version from `standalone/src-tauri/tauri.conf.json` so you ## 2. Decide the version bump -mouseterm uses **breaking.added.bugfix** semantics (semver-shaped, but named for what each segment means here): +dormouse uses **breaking.added.bugfix** semantics (semver-shaped, but named for what each segment means here): - **breaking** (major) — bump if any change breaks behavior users rely on, removes a feature, or changes a VSCode extension contribution point in an incompatible way - **added** (minor) — bump if any change adds a new user-facing feature, with no breaking changes diff --git a/.vscode/launch.json b/.vscode/launch.json index be3e16f1..69eb963f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "args": ["--extensionDevelopmentPath=${workspaceFolder}/vscode-ext"], "outFiles": ["${workspaceFolder}/vscode-ext/dist/**/*.js"], - "preLaunchTask": "build-mouseterm-vscode" + "preLaunchTask": "build-dormouse-vscode" } ] } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index f4441bb6..10e60b44 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -2,7 +2,7 @@ "version": "2.0.0", "tasks": [ { - "label": "build-mouseterm-vscode", + "label": "build-dormouse-vscode", "type": "shell", "command": "pnpm build:vscode", "group": "build", diff --git a/docs/specs/tutorial.md b/docs/specs/tutorial.md index 133c6c54..fa1e88ea 100644 --- a/docs/specs/tutorial.md +++ b/docs/specs/tutorial.md @@ -8,7 +8,7 @@ Three browser-side pieces in `website/src/lib/`, mirroring the pattern in `websi - **`tut-runner.ts`** (`TutRunner`) — alt-screen TUI. Subscribes to `TutorialState` and re-renders whenever progress changes. Routes input bytes via `FakePtyAdapter.writePty(id, …)`. - **`tut-detector.ts`** (`TutDetector`) — wires app events to `TutorialState.markComplete(id)`. Subscribes to `DockviewApi.onDidActivePanelChange`, the `WallEvent` stream, the `subscribeToActivity` store from `dormouse-lib/lib/terminal-registry`, and the `subscribeToMouseSelection` store from `dormouse-lib/lib/mouse-selection`. -- **`tutorial-state.ts`** (`TutorialState`) — single in-memory progress store, persisted as a JSON array of completed item ids under the `mouseterm-tut-v3` localStorage key. +- **`tutorial-state.ts`** (`TutorialState`) — single in-memory progress store, persisted as a JSON array of completed item ids under the `dormouse-tut-v3` localStorage key. - **`tut-items.ts`** — section + item definitions (titles, hints) shared by runner and detector. Item ids are stable; they are the localStorage key suffixes. ## Layout @@ -100,8 +100,8 @@ While the Copy paste section is open, pressing `p` toggles the **Place To Paste* ## Storage -- Completion: `localStorage["mouseterm-tut-v3"] = JSON.stringify([...completedItemIds])`. Removed on `TutorialState.reset()`. Unknown ids in a stored payload are filtered out on load, so renaming an id is a one-way reset for that item. -- Legacy keys `mouseterm-tutorial-step-N` and `mouseterm-tut-v2-*` from previous designs are not read; new playground sessions get a fresh start. +- Completion: `localStorage["dormouse-tut-v3"] = JSON.stringify([...completedItemIds])`. Removed on `TutorialState.reset()`. Unknown ids in a stored payload are filtered out on load, so renaming an id is a one-way reset for that item. +- Legacy keys `dormouse-tutorial-step-N` and `dormouse-tut-v2-*` from previous designs are not read; new playground sessions get a fresh start. ## Theme Picker diff --git a/docs/specs/vscode.md b/docs/specs/vscode.md index ff626839..845b46c2 100644 --- a/docs/specs/vscode.md +++ b/docs/specs/vscode.md @@ -213,7 +213,7 @@ pnpm dogfood:vscode = build + package VSIX + install locally (then: Cmd+Shift+P -> "Developer: Reload Window" to pick up changes) F5 in VS Code = launch Extension Development Host (see .vscode/launch.json) - (runs preLaunchTask "build-mouseterm-vscode" from .vscode/tasks.json, + (runs preLaunchTask "build-dormouse-vscode" from .vscode/tasks.json, which just calls `pnpm build:vscode`, then opens a new VS Code window with the extension loaded) ``` @@ -230,13 +230,13 @@ Set context keys so menus and extensions can target Dormouse state: ```typescript // Set when any Dormouse webview has focus -vscode.commands.executeCommand('setContext', 'mouseterm.active', true); +vscode.commands.executeCommand('setContext', 'dormouse.active', true); // Set when Dormouse is in passthrough/terminal mode (keys go to PTY) -vscode.commands.executeCommand('setContext', 'mouseterm.mode', 'terminal'); +vscode.commands.executeCommand('setContext', 'dormouse.mode', 'terminal'); // Set when Dormouse is in normal/navigation mode (keys go to Dormouse UI) -vscode.commands.executeCommand('setContext', 'mouseterm.mode', 'normal'); +vscode.commands.executeCommand('setContext', 'dormouse.mode', 'normal'); ``` ### Commands @@ -244,19 +244,19 @@ vscode.commands.executeCommand('setContext', 'mouseterm.mode', 'normal'); | Command | Description | |---------|-------------| | `dormouse.focus` | Focus the Dormouse panel view | -| `mouseterm.newPane` | Split a new pane in Dormouse | -| `mouseterm.closePane` | Close the focused pane | -| `mouseterm.nextPane` | Focus next pane | -| `mouseterm.prevPane` | Focus previous pane | -| `mouseterm.enterTerminalMode` | Switch to passthrough mode | -| `mouseterm.enterNormalMode` | Switch to navigation mode | -| `mouseterm.listSessions` | Show QuickPick of all live PTY sessions | -| `mouseterm.reattach` | Reattach a minimized PTY to a pane | +| `dormouse.newPane` | Split a new pane in Dormouse | +| `dormouse.closePane` | Close the focused pane | +| `dormouse.nextPane` | Focus next pane | +| `dormouse.prevPane` | Focus previous pane | +| `dormouse.enterTerminalMode` | Switch to passthrough mode | +| `dormouse.enterNormalMode` | Switch to navigation mode | +| `dormouse.listSessions` | Show QuickPick of all live PTY sessions | +| `dormouse.reattach` | Reattach a minimized PTY to a pane | ### Not yet implemented - `TerminalProfileProvider` not registered — Dormouse doesn't appear in the terminal `+` dropdown -- Context keys not set (`mouseterm.active`, `mouseterm.mode`) — needed for conditional keybindings -- Commands not registered: `mouseterm.newPane`, `closePane`, `nextPane`, `prevPane`, `enterTerminalMode`, `enterNormalMode`, `listSessions`, `reattach` +- Context keys not set (`dormouse.active`, `dormouse.mode`) — needed for conditional keybindings +- Commands not registered: `dormouse.newPane`, `closePane`, `nextPane`, `prevPane`, `enterTerminalMode`, `enterNormalMode`, `listSessions`, `reattach` - No status bar item showing active session count - No QuickPick for listing/reattaching PTY sessions diff --git a/lib/src/components/ThemeDebugger.tsx b/lib/src/components/ThemeDebugger.tsx index c939c893..ed07d6dd 100644 --- a/lib/src/components/ThemeDebugger.tsx +++ b/lib/src/components/ThemeDebugger.tsx @@ -43,7 +43,7 @@ function originClass(origin: VscodeThemeVarTraceOrigin | VisibleVarOrigin): stri case 'host-provided': return 'text-success'; case 'registry-default': - case 'mouseterm-materialized': + case 'dormouse-materialized': return '[color:var(--vscode-terminal-ansiYellow)]'; case 'fallback': return 'text-muted'; diff --git a/lib/src/components/Wall.tsx b/lib/src/components/Wall.tsx index e94e6b60..f96a9a8e 100644 --- a/lib/src/components/Wall.tsx +++ b/lib/src/components/Wall.tsx @@ -86,7 +86,7 @@ export { TerminalPaneHeader } from './wall/TerminalPaneHeader'; const dormouseTheme: DockviewTheme = { ...themeAbyss, - name: 'mouseterm', + name: 'dormouse', gap: 6, dndOverlayMounting: 'absolute', dndPanelOverlay: 'group', diff --git a/lib/src/lib/terminal-state.test.ts b/lib/src/lib/terminal-state.test.ts index 6dd6027f..b2f773c0 100644 --- a/lib/src/lib/terminal-state.test.ts +++ b/lib/src/lib/terminal-state.test.ts @@ -142,13 +142,13 @@ describe('terminal command state reducer', () => { let state = createTerminalPaneState(); state = reduceTerminalState(state, { type: 'title', title: { title: 'zsh', source: 'osc0', updatedAt: 1 } }); state = reduceTerminalState(state, { type: 'title', title: { title: 'vim', source: 'osc2', updatedAt: 2 } }); - state = reduceTerminalState(state, { type: 'title', title: { title: 'mouseterm', source: 'osc0', updatedAt: 3 } }); + state = reduceTerminalState(state, { type: 'title', title: { title: 'dormouse', source: 'osc0', updatedAt: 3 } }); - expect(state.title).toEqual({ title: 'mouseterm', source: 'osc0', updatedAt: 3 }); - expect(state.titleCandidates.osc0).toEqual({ title: 'mouseterm', source: 'osc0', updatedAt: 3 }); + expect(state.title).toEqual({ title: 'dormouse', source: 'osc0', updatedAt: 3 }); + expect(state.titleCandidates.osc0).toEqual({ title: 'dormouse', source: 'osc0', updatedAt: 3 }); expect(state.titleCandidates.osc2).toEqual({ title: 'vim', source: 'osc2', updatedAt: 2 }); expect(titleCandidatesForDisplay(state).map((candidate) => [candidate.source, candidate.title])).toEqual([ - ['osc0', 'mouseterm'], + ['osc0', 'dormouse'], ['osc2', 'vim'], ]); }); @@ -256,11 +256,11 @@ describe('header and grouping derivation', () => { it('lets fresh app-sent terminal titles override running command labels', () => { const pane = reduceTerminalState( runningPane('/repo/app', 'lazygit'), - { type: 'title', title: { title: 'lazygit: mouseterm', source: 'osc0', updatedAt: 2 } }, + { type: 'title', title: { title: 'lazygit: dormouse', source: 'osc0', updatedAt: 2 } }, ); expect(deriveHeader(pane, [pane])).toEqual({ - primary: 'lazygit: mouseterm', + primary: 'lazygit: dormouse', }); }); @@ -358,23 +358,23 @@ describe('header and grouping derivation', () => { it('uses the in-run app-sent title as `<idle> ${LAST_TITLE}`', () => { let pane = runningPane('/repo/app', 'lazygit'); - pane = reduceTerminalState(pane, { type: 'title', title: { title: 'lazygit: mouseterm', source: 'osc0', updatedAt: 2 } }); + pane = reduceTerminalState(pane, { type: 'title', title: { title: 'lazygit: dormouse', source: 'osc0', updatedAt: 2 } }); pane = reduceTerminalState(pane, { type: 'commandFinish', exitCode: 0 }, { now: () => 3 }); expect(deriveHeader(pane, [pane])).toEqual({ - primary: `${DEFAULT_IDLE_TITLE} lazygit: mouseterm`, + primary: `${DEFAULT_IDLE_TITLE} lazygit: dormouse`, }); }); it('ignores titles emitted after the last command finished when deriving LAST_TITLE', () => { let pane = runningPane('/repo/app', 'lazygit'); - pane = reduceTerminalState(pane, { type: 'title', title: { title: 'lazygit: mouseterm', source: 'osc0', updatedAt: 2 } }); + pane = reduceTerminalState(pane, { type: 'title', title: { title: 'lazygit: dormouse', source: 'osc0', updatedAt: 2 } }); pane = reduceTerminalState(pane, { type: 'commandFinish', exitCode: 0 }, { now: () => 3 }); // Shell sets the title back to its default after the command exits. pane = reduceTerminalState(pane, { type: 'title', title: { title: 'zsh', source: 'osc0', updatedAt: 4 } }); expect(deriveHeader(pane, [pane])).toEqual({ - primary: `${DEFAULT_IDLE_TITLE} lazygit: mouseterm`, + primary: `${DEFAULT_IDLE_TITLE} lazygit: dormouse`, }); }); diff --git a/lib/src/lib/terminal-theme.ts b/lib/src/lib/terminal-theme.ts index e5b704f7..8e1b2681 100644 --- a/lib/src/lib/terminal-theme.ts +++ b/lib/src/lib/terminal-theme.ts @@ -45,7 +45,7 @@ export function paintTerminalHost(element: HTMLDivElement, terminal: Terminal, b const hosts = element.querySelectorAll<HTMLElement>(XTERM_HOST_SELECTOR); if (hosts.length === 0 && xtermElement && !xtermSelectorWarned) { xtermSelectorWarned = true; - console.warn(`[mouseterm] paintTerminalHost: no elements matched ${XTERM_HOST_SELECTOR} - xterm DOM may have changed.`); + console.warn(`[dormouse] paintTerminalHost: no elements matched ${XTERM_HOST_SELECTOR} - xterm DOM may have changed.`); return; } hosts.forEach((el) => { diff --git a/lib/src/lib/themes/diagnostics.ts b/lib/src/lib/themes/diagnostics.ts index 281b3715..4020d49d 100644 --- a/lib/src/lib/themes/diagnostics.ts +++ b/lib/src/lib/themes/diagnostics.ts @@ -11,7 +11,7 @@ import { type VscodeThemeVarTrace, } from './vscode-color-resolver'; -export type VisibleVarOrigin = 'host-provided' | 'mouseterm-materialized' | 'missing'; +export type VisibleVarOrigin = 'host-provided' | 'dormouse-materialized' | 'missing'; export interface ThemeMetadataSnapshot { id: string; @@ -152,9 +152,9 @@ function captureVisibleVars( let origin: VisibleVarOrigin = value ? 'host-provided' : 'missing'; if (value && materialized.get(name) === value) { - origin = 'mouseterm-materialized'; + origin = 'dormouse-materialized'; } else if (value && applied && !applied.theme.vars[name] && applied.resolvedVars[name] === value) { - origin = 'mouseterm-materialized'; + origin = 'dormouse-materialized'; } return { diff --git a/lib/src/stories/ShellCwd.stories.tsx b/lib/src/stories/ShellCwd.stories.tsx index 1201fde5..8a017aef 100644 --- a/lib/src/stories/ShellCwd.stories.tsx +++ b/lib/src/stories/ShellCwd.stories.tsx @@ -74,7 +74,7 @@ export const CwdSourcesAndPathKinds: Story = storyFor([ caseState('cwd-osc99-drive', 'OSC 9;9 drive', idle({ cwd: osc99('C:\\Users\\me\\repo') }), 'Windows drive-letter path'), caseState('cwd-osc99-unc', 'OSC 9;9 UNC', idle({ cwd: osc99('\\\\server\\share\\repo') }), 'Windows UNC path'), caseState('cwd-osc99-wsl', 'OSC 9;9 WSL-like', idle({ cwd: osc99('/mnt/c/Users/me/repo') }), 'Unknown path kind by design'), - caseState('cwd-osc633', 'OSC 633 Cwd', idle({ cwd: osc633('/workspaces/mouseterm') }), 'VS Code shell integration CWD'), + caseState('cwd-osc633', 'OSC 633 Cwd', idle({ cwd: osc633('/workspaces/dormouse') }), 'VS Code shell integration CWD'), caseState('cwd-osc1337', 'OSC 1337 CurrentDir', idle({ cwd: osc1337('/Users/me/iterm-app') }), 'iTerm2 CurrentDir compatibility'), ]); @@ -369,7 +369,7 @@ function titleCandidateState(): TerminalPaneState { const pane = running('/repo/app', 'npm run dev'); const candidates = { user: terminalTitleAt('Pinned production API', 'user', BASE_TIME + 6_000), - osc0: terminalTitleAt('mouseterm', 'osc0', BASE_TIME + 1_000), + osc0: terminalTitleAt('dormouse', 'osc0', BASE_TIME + 1_000), osc2: terminalTitleAt('zsh', 'osc2', BASE_TIME + 2_000), osc9: terminalTitleAt('Build finished', 'osc9', BASE_TIME + 5_000), osc99: terminalTitleAt('Codex waiting', 'osc99', BASE_TIME + 4_000), diff --git a/scripts/patch-nsis-paths.pl b/scripts/patch-nsis-paths.pl index 6aecb7e8..e5a7a84a 100644 --- a/scripts/patch-nsis-paths.pl +++ b/scripts/patch-nsis-paths.pl @@ -19,7 +19,7 @@ close $fh; # Extract CI checkout root from MAINBINARYSRCPATH. -# The path looks like: "D:\a\mouseterm\mouseterm\standalone\src-tauri\target\...\mouseterm.exe" +# The path looks like: "D:\a\dormouse\dormouse\standalone\src-tauri\target\...\dormouse.exe" # We want everything before \src-tauri\ (including \standalone) because # actions/upload-artifact strips the common ancestor (standalone/) from paths. my $ci_root; diff --git a/standalone/sidecar/clipboard-ops.js b/standalone/sidecar/clipboard-ops.js index 5a0b8676..c534af2d 100644 --- a/standalone/sidecar/clipboard-ops.js +++ b/standalone/sidecar/clipboard-ops.js @@ -239,7 +239,7 @@ async function readClipboardImageAsFilePath(runtime = {}) { let dir = null; let out = null; try { - dir = await fsp.mkdtemp(path.join(osModule.tmpdir(), 'mouseterm-drops-')); + dir = await fsp.mkdtemp(path.join(osModule.tmpdir(), 'dormouse-drops-')); await fsp.chmod?.(dir, 0o700); out = path.join(dir, `${cryptoModule.randomUUID()}-clipboard.png`); const ok = platform === 'darwin' ? await readImageMac(out, runtime) diff --git a/standalone/sidecar/clipboard-ops.test.js b/standalone/sidecar/clipboard-ops.test.js index f35d44d5..4402a8cc 100644 --- a/standalone/sidecar/clipboard-ops.test.js +++ b/standalone/sidecar/clipboard-ops.test.js @@ -159,10 +159,10 @@ test('readClipboardImageAsFilePath on mac returns temp path on success', async ( return { stdout: 'ok\n' }; }, }); - const expected = path.join('/t', 'mouseterm-drops-dir-0', 'uuid-I-clipboard.png'); + const expected = path.join('/t', 'dormouse-drops-dir-0', 'uuid-I-clipboard.png'); assert.equal(result, expected); assert.deepEqual(fs.chmods, [ - [path.join('/t', 'mouseterm-drops-dir-0'), 0o700], + [path.join('/t', 'dormouse-drops-dir-0'), 0o700], [expected, 0o600], ]); // Cleanup was scheduled, but not yet run: the temp file still exists. @@ -175,7 +175,7 @@ test('readClipboardImageAsFilePath on mac returns temp path on success', async ( await new Promise((r) => setImmediate(r)); await new Promise((r) => setImmediate(r)); assert.deepEqual(fs.unlinks, [expected]); - assert.deepEqual(fs.rmdirs, [path.join('/t', 'mouseterm-drops-dir-0')]); + assert.deepEqual(fs.rmdirs, [path.join('/t', 'dormouse-drops-dir-0')]); }); test('readClipboardImageAsFilePath returns null when osascript returns empty', async () => { @@ -188,7 +188,7 @@ test('readClipboardImageAsFilePath returns null when osascript returns empty', a exec: async () => ({ stdout: '' }), }); assert.equal(result, null); - assert.deepEqual(fs.rmdirs, [path.join('/t', 'mouseterm-drops-dir-0')]); + assert.deepEqual(fs.rmdirs, [path.join('/t', 'dormouse-drops-dir-0')]); }); test('readClipboardText on mac shells out to pbpaste', async () => { @@ -279,7 +279,7 @@ test('readClipboardImageAsFilePath on linux writes buffer from exec stdout', asy throw new Error('no tool'); }, }); - assert.equal(result, path.join('/t', 'mouseterm-drops-dir-0', 'uuid-L-clipboard.png')); + assert.equal(result, path.join('/t', 'dormouse-drops-dir-0', 'uuid-L-clipboard.png')); assert.equal(fs.writes.length, 1); assert.deepEqual(fs.writes[0][2], { mode: 0o600 }); }); diff --git a/standalone/src-tauri/Cargo.lock b/standalone/src-tauri/Cargo.lock index 4ce2a9ed..4fa405df 100644 --- a/standalone/src-tauri/Cargo.lock +++ b/standalone/src-tauri/Cargo.lock @@ -1943,7 +1943,7 @@ dependencies = [ ] [[package]] -name = "mouseterm" +name = "dormouse" version = "0.9.1" dependencies = [ "process-wrap", diff --git a/website/scripts/generate-deps.js b/website/scripts/generate-deps.js index 11a1c5a8..b7f40533 100644 --- a/website/scripts/generate-deps.js +++ b/website/scripts/generate-deps.js @@ -8,9 +8,9 @@ const repoRoot = resolve(__dirname, "../.."); const outPath = resolve(__dirname, "../src/data/dependencies.json"); const themeExtensionsPath = resolve(repoRoot, "lib/src/lib/themes/bundled-extensions.json"); const productDependencyFilters = [ - "mouseterm", - "mouseterm-standalone", - "mouseterm-lib", + "dormouse", + "dormouse-standalone", + "dormouse-lib", ]; function getInstalledStoreDir() { From e27db94a8f7a2a80cdfde7479a1c4166aa222c50 Mon Sep 17 00:00:00 2001 From: Ned Twigg <ned.twigg@diffplug.com> Date: Sun, 17 May 2026 22:01:57 -0700 Subject: [PATCH 13/24] `MouseTerm` -> `Dormouse` --- .impeccable/design.json | 4 ++-- .vscode/launch.json | 2 +- lib/.storybook/themes.ts | 6 +++--- lib/index.html | 2 +- lib/scripts/bundle-themes.mjs | 2 +- lib/src/components/ThemePicker.tsx | 6 +++--- lib/src/components/theme-picker/ThemeSwatch.tsx | 4 ++-- lib/src/lib/themes/apply.ts | 10 +++++----- lib/src/lib/themes/convert.ts | 2 +- lib/src/lib/themes/diagnostics.ts | 2 +- lib/src/lib/themes/flatten-alpha.ts | 4 ++-- lib/src/lib/themes/index.ts | 2 +- lib/src/lib/themes/openvsx.ts | 10 +++++----- lib/src/lib/themes/store.ts | 16 ++++++++-------- lib/src/lib/themes/types.ts | 2 +- lib/src/theme.css | 4 ++-- website/public/standalone-latest.json | 6 +++--- .../lib/__snapshots__/tut-runner.test.ts.snap | 4 ++-- website/src/lib/ascii-splash-runner.ts | 4 ++-- website/src/lib/changelog-runner.ts | 2 +- website/src/lib/tut-items.ts | 4 ++-- website/src/lib/tut-runner.test.ts | 2 +- website/src/lib/tut-runner.ts | 2 +- website/src/pages/Changelog.tsx | 2 +- 24 files changed, 52 insertions(+), 52 deletions(-) diff --git a/.impeccable/design.json b/.impeccable/design.json index 57df9e30..e1e2c7a4 100644 --- a/.impeccable/design.json +++ b/.impeccable/design.json @@ -1,7 +1,7 @@ { "schemaVersion": 2, "generatedAt": "2026-05-11T00:00:00Z", - "title": "Design System: MouseTerm", + "title": "Design System: Dormouse", "extensions": { "colorMeta": { "app-bg": { @@ -236,7 +236,7 @@ ], "narrative": { "northStar": "The Native Tenant", - "overview": "MouseTerm is a tenant in someone else's house. The house is VSCode. The user picked the furniture (their theme), the lighting (their mode), the typography (their editor font). MouseTerm moves in, multiplies what the user can do with their terminals, and leaves the decor alone. The system is intentionally minimal and bg-only. Chrome recedes; terminals are the content. Hierarchy is conveyed through background shifts between header-active-bg and header-inactive-bg, not through borders, shadows, or accent stripes. Status is conveyed through shape and position and through the active terminal palette's own ANSI red/green/yellow, not through a separate design-system palette.", + "overview": "Dormouse is a tenant in someone else's house. The house is VSCode. The user picked the furniture (their theme), the lighting (their mode), the typography (their editor font). Dormouse moves in, multiplies what the user can do with their terminals, and leaves the decor alone. The system is intentionally minimal and bg-only. Chrome recedes; terminals are the content. Hierarchy is conveyed through background shifts between header-active-bg and header-inactive-bg, not through borders, shadows, or accent stripes. Status is conveyed through shape and position and through the active terminal palette's own ANSI red/green/yellow, not through a separate design-system palette.", "keyCharacteristics": [ "Host-theme-driven palette: every color is a var(--vscode-*) passthrough.", "Bg-only chrome: no decorative borders, no resting shadows, no accent stripes.", diff --git a/.vscode/launch.json b/.vscode/launch.json index 69eb963f..819afd94 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,7 +2,7 @@ "version": "0.2.0", "configurations": [ { - "name": "MouseTerm Extension", + "name": "Dormouse Extension", "type": "extensionHost", "request": "launch", "args": ["--extensionDevelopmentPath=${workspaceFolder}/vscode-ext"], diff --git a/lib/.storybook/themes.ts b/lib/.storybook/themes.ts index a26fa129..8f159661 100644 --- a/lib/.storybook/themes.ts +++ b/lib/.storybook/themes.ts @@ -3,10 +3,10 @@ * applyTheme(), so isolated stories receive VSCode registry defaults too. */ import _bundled from '../src/lib/themes/bundled.json'; -import type { MouseTermTheme } from '../src/lib/themes/types'; +import type { DormouseTheme } from '../src/lib/themes/types'; import { completeThemeVars } from '../src/lib/themes/vscode-color-resolver'; -const bundled = _bundled as unknown as MouseTermTheme[]; +const bundled = _bundled as unknown as DormouseTheme[]; const STORYBOOK_HOST_TYPOGRAPHY_VARS: Record<string, string> = { '--vscode-font-size': '13px', @@ -17,7 +17,7 @@ const STORYBOOK_HOST_TYPOGRAPHY_VARS: Record<string, string> = { }; export const VSCODE_THEMES: Record<string, Record<string, string>> = {}; -export const VSCODE_THEME_TYPES: Record<string, MouseTermTheme['type']> = {}; +export const VSCODE_THEME_TYPES: Record<string, DormouseTheme['type']> = {}; for (const theme of bundled) { VSCODE_THEME_TYPES[theme.label] = theme.type; VSCODE_THEMES[theme.label] = completeThemeVars( diff --git a/lib/index.html b/lib/index.html index 42d08170..bb127d78 100644 --- a/lib/index.html +++ b/lib/index.html @@ -3,7 +3,7 @@ <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> - <title>MouseTerm + Dormouse
diff --git a/lib/scripts/bundle-themes.mjs b/lib/scripts/bundle-themes.mjs index 0e3f177a..e5827e4e 100644 --- a/lib/scripts/bundle-themes.mjs +++ b/lib/scripts/bundle-themes.mjs @@ -45,7 +45,7 @@ const EXCLUDED_THEMES = new Set([ ]); /** - * VSCode theme color keys consumed by MouseTerm. + * VSCode theme color keys consumed by Dormouse. * Keep in sync with lib/src/lib/themes/convert.ts. */ const CONSUMED_KEYS = new Set([ diff --git a/lib/src/components/ThemePicker.tsx b/lib/src/components/ThemePicker.tsx index cc114640..b87c4cc6 100644 --- a/lib/src/components/ThemePicker.tsx +++ b/lib/src/components/ThemePicker.tsx @@ -1,6 +1,6 @@ import { useCallback, useId, useRef, useState } from 'react'; import { CaretDownIcon } from '@phosphor-icons/react'; -import type { MouseTermTheme } from '../lib/themes'; +import type { DormouseTheme } from '../lib/themes'; import { applyTheme, getAllThemes, @@ -32,7 +32,7 @@ export function ThemePicker({ variant, className = '', defaultThemeId }: ThemePi // the first paint already has --vscode-* on body — eliminates the flash of // unstyled chrome on the website playground where ThemePicker mounts before // any other entry point has a chance to apply a theme. - const initialState = useRef<{ themes: MouseTermTheme[]; activeId: string }>(null); + const initialState = useRef<{ themes: DormouseTheme[]; activeId: string }>(null); if (initialState.current === null) { const restored = restoreActiveTheme(defaultThemeId); const themes = getAllThemes(); @@ -66,7 +66,7 @@ export function ThemePicker({ variant, className = '', defaultThemeId }: ThemePi setOpen(false); }; - const deleteTheme = (theme: MouseTermTheme) => { + const deleteTheme = (theme: DormouseTheme) => { if (theme.origin.kind !== 'installed') return; const confirmed = window.confirm(`Delete "${theme.label}"?`); if (!confirmed) return; diff --git a/lib/src/components/theme-picker/ThemeSwatch.tsx b/lib/src/components/theme-picker/ThemeSwatch.tsx index 375ddaca..951b1861 100644 --- a/lib/src/components/theme-picker/ThemeSwatch.tsx +++ b/lib/src/components/theme-picker/ThemeSwatch.tsx @@ -1,7 +1,7 @@ -import type { MouseTermTheme } from '../../lib/themes'; +import type { DormouseTheme } from '../../lib/themes'; import { themePickerStyles as styles } from './styles'; -export function ThemeSwatch({ theme, size }: { theme: MouseTermTheme; size: 'sm' | 'md' }) { +export function ThemeSwatch({ theme, size }: { theme: DormouseTheme; size: 'sm' | 'md' }) { const swatchClass = size === 'sm' ? 'h-3.5 w-3.5' : 'h-4 w-4'; return ( diff --git a/lib/src/lib/themes/apply.ts b/lib/src/lib/themes/apply.ts index 62fa0a44..14867f12 100644 --- a/lib/src/lib/themes/apply.ts +++ b/lib/src/lib/themes/apply.ts @@ -1,4 +1,4 @@ -import type { MouseTermTheme } from './types'; +import type { DormouseTheme } from './types'; import { getAllThemes, getStoredActiveThemeId, setActiveThemeId } from './store'; import { completeThemeVars } from './vscode-color-resolver'; import { flattenSelectionAlpha } from './flatten-alpha'; @@ -6,7 +6,7 @@ import { flattenSelectionAlpha } from './flatten-alpha'; let appliedThemeSnapshot: AppliedThemeSnapshot | null = null; export interface AppliedThemeSnapshot { - theme: MouseTermTheme; + theme: DormouseTheme; providedVars: Record; resolvedVars: Record; } @@ -19,7 +19,7 @@ const HOST_TYPOGRAPHY_VARS: Record = { "'SF Mono', Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace", }; -export function applyTheme(theme: MouseTermTheme): void { +export function applyTheme(theme: DormouseTheme): void { if (typeof document === 'undefined') return; if (theme === appliedThemeSnapshot?.theme) return; @@ -34,7 +34,7 @@ export function applyTheme(theme: MouseTermTheme): void { const providedVars = { ...HOST_TYPOGRAPHY_VARS, ...theme.vars }; const vars = completeThemeVars(providedVars, theme.type); // Theme authors give list.*SelectionBackground alpha because VSCode renders - // it as an overlay on the sidebar. MouseTerm uses it as a solid AppBar / + // it as an overlay on the sidebar. Dormouse uses it as a solid AppBar / // tab fill, so flatten the alpha over sideBar.background here — otherwise // whatever sits behind the surface bleeds through (Selenized Dark's bright // cyan AppBar, for instance). @@ -58,7 +58,7 @@ export function applyTheme(theme: MouseTermTheme): void { * first bundled theme. Idempotent and safe to call before render so the * first paint already has --vscode-* set on body. Returns the theme that was * applied, or null when no themes are available (e.g. SSR). */ -export function restoreActiveTheme(defaultThemeId?: string): MouseTermTheme | null { +export function restoreActiveTheme(defaultThemeId?: string): DormouseTheme | null { const all = getAllThemes(); const find = (id: string | null | undefined) => (id ? all.find((t) => t.id === id) : undefined); const theme = find(getStoredActiveThemeId()) ?? find(defaultThemeId) ?? all[0]; diff --git a/lib/src/lib/themes/convert.ts b/lib/src/lib/themes/convert.ts index 0d45d71e..44545ed8 100644 --- a/lib/src/lib/themes/convert.ts +++ b/lib/src/lib/themes/convert.ts @@ -6,7 +6,7 @@ * 2. getTerminalTheme() in terminal-theme.ts — ANSI, cursor, selection */ -/** VSCode theme color keys consumed by MouseTerm. Derived from theme.css, +/** VSCode theme color keys consumed by Dormouse. Derived from theme.css, * useDynamicPalette, ThemePicker inline styles, SelectionOverlay, * terminal-theme, and the VSCode fallback resolver. */ export const CONSUMED_VSCODE_KEYS: readonly string[] = [ diff --git a/lib/src/lib/themes/diagnostics.ts b/lib/src/lib/themes/diagnostics.ts index 4020d49d..582fab18 100644 --- a/lib/src/lib/themes/diagnostics.ts +++ b/lib/src/lib/themes/diagnostics.ts @@ -247,7 +247,7 @@ function formatTrace(trace: VscodeThemeVarTrace): string { function buildReport(snapshot: Omit): string { const lines: string[] = []; - lines.push('MouseTerm theme diagnostic'); + lines.push('Dormouse theme diagnostic'); lines.push(`capturedAt: ${snapshot.capturedAt}`); lines.push(`themeKind: ${snapshot.themeKind}`); lines.push(`activeTheme: ${snapshot.activeTheme ? `${snapshot.activeTheme.label} (${snapshot.activeTheme.origin})` : 'VSCode host theme'}`); diff --git a/lib/src/lib/themes/flatten-alpha.ts b/lib/src/lib/themes/flatten-alpha.ts index c1a5cfd6..1d9d4779 100644 --- a/lib/src/lib/themes/flatten-alpha.ts +++ b/lib/src/lib/themes/flatten-alpha.ts @@ -1,6 +1,6 @@ /* Composite a translucent CSS color over an opaque base, returning an opaque * hex result. Used to flatten VSCode tokens that carry alpha (e.g. Selenized - * Dark's list.activeSelectionBackground = #0096f588) when MouseTerm applies + * Dark's list.activeSelectionBackground = #0096f588) when Dormouse applies * them as solid surface fills — the AppBar, dockview tabs, etc. all expect a * fully opaque color, but VSCode authors selection tints with alpha because * VSCode itself renders them as overlays on the sidebar background. */ @@ -59,7 +59,7 @@ export function flattenAlpha(value: string, base: string): string { }); } -/* VSCode tokens that MouseTerm uses as solid surface fills but whose theme +/* VSCode tokens that Dormouse uses as solid surface fills but whose theme * authors commonly carry alpha. Flattened against sideBar.background — the * surface VSCode itself composites the file-tree selection over. */ const FLATTEN_OVER_SIDEBAR: readonly string[] = [ diff --git a/lib/src/lib/themes/index.ts b/lib/src/lib/themes/index.ts index 59ee1543..5be40e8a 100644 --- a/lib/src/lib/themes/index.ts +++ b/lib/src/lib/themes/index.ts @@ -1,4 +1,4 @@ -export type { MouseTermTheme, BundledOrigin, InstalledOrigin } from './types'; +export type { DormouseTheme, BundledOrigin, InstalledOrigin } from './types'; export { CONSUMED_VSCODE_KEYS, convertVscodeThemeColors, uiThemeToType } from './convert'; export { applyTheme, getAppliedThemeSnapshot, restoreActiveTheme } from './apply'; export type { AppliedThemeSnapshot } from './apply'; diff --git a/lib/src/lib/themes/openvsx.ts b/lib/src/lib/themes/openvsx.ts index 10fffb3d..43a091b1 100644 --- a/lib/src/lib/themes/openvsx.ts +++ b/lib/src/lib/themes/openvsx.ts @@ -2,12 +2,12 @@ * Runtime OpenVSX theme installer. * * Searches for theme extensions, downloads VSIX files, extracts theme - * JSONs in the browser, and converts them to MouseTermTheme objects. + * JSONs in the browser, and converts them to DormouseTheme objects. * * fflate is dynamically imported so it doesn't affect initial bundle size. */ -import type { MouseTermTheme } from './types'; +import type { DormouseTheme } from './types'; import { convertVscodeThemeColors, uiThemeToType } from './convert'; const OPENVSX_API = 'https://open-vsx.org/api'; @@ -56,12 +56,12 @@ function slugify(label: string): string { /** * Download a theme extension from OpenVSX and return all theme variants - * as MouseTermTheme objects ready for installation. + * as DormouseTheme objects ready for installation. */ export async function fetchExtensionThemes( namespace: string, name: string, -): Promise { +): Promise { // 1. Get latest version metadata const metaRes = await fetch(`${OPENVSX_API}/${namespace}/${name}/latest`); if (!metaRes.ok) throw new Error(`OpenVSX metadata failed: ${metaRes.status}`); @@ -108,7 +108,7 @@ export async function fetchExtensionThemes( const { parse: parseJsonc } = await import('jsonc-parser'); // 6. Convert each theme variant - const themes: MouseTermTheme[] = []; + const themes: DormouseTheme[] = []; for (const contrib of themeContribs) { const themePath = `extension/${contrib.path.replace(/^\.\//, '')}`; const themeData = entries[themePath]; diff --git a/lib/src/lib/themes/store.ts b/lib/src/lib/themes/store.ts index f6cf95e3..b627d958 100644 --- a/lib/src/lib/themes/store.ts +++ b/lib/src/lib/themes/store.ts @@ -1,7 +1,7 @@ -import type { MouseTermTheme } from './types'; +import type { DormouseTheme } from './types'; // JSON import types are inferred too narrowly — cast at the boundary. import _bundledThemes from './bundled.json'; -const bundledThemes = _bundledThemes as unknown as MouseTermTheme[]; +const bundledThemes = _bundledThemes as unknown as DormouseTheme[]; const INSTALLED_KEY = 'dormouse:installed-themes'; const ACTIVE_KEY = 'dormouse:active-theme'; @@ -18,30 +18,30 @@ function getStorage(): Storage | null { return storage; } -export function getBundledThemes(): MouseTermTheme[] { +export function getBundledThemes(): DormouseTheme[] { return bundledThemes; } -export function getInstalledThemes(): MouseTermTheme[] { +export function getInstalledThemes(): DormouseTheme[] { const storage = getStorage(); if (!storage) return []; try { const raw = storage.getItem(INSTALLED_KEY); - return raw ? (JSON.parse(raw) as MouseTermTheme[]) : []; + return raw ? (JSON.parse(raw) as DormouseTheme[]) : []; } catch { return []; } } -export function getAllThemes(): MouseTermTheme[] { +export function getAllThemes(): DormouseTheme[] { return [...getBundledThemes(), ...getInstalledThemes()]; } -export function getTheme(id: string): MouseTermTheme | undefined { +export function getTheme(id: string): DormouseTheme | undefined { return getAllThemes().find((t) => t.id === id); } -export function addInstalledTheme(theme: MouseTermTheme): void { +export function addInstalledTheme(theme: DormouseTheme): void { const storage = getStorage(); if (!storage) return; const installed = getInstalledThemes().filter((t) => t.id !== theme.id); diff --git a/lib/src/lib/themes/types.ts b/lib/src/lib/themes/types.ts index 7b4511ee..60a819b1 100644 --- a/lib/src/lib/themes/types.ts +++ b/lib/src/lib/themes/types.ts @@ -1,4 +1,4 @@ -export interface MouseTermTheme { +export interface DormouseTheme { /** Stable unique ID, e.g. "GitHub.github-vscode-theme.github-dark-default" */ id: string; /** Human-readable label from the VSCode theme */ diff --git a/lib/src/theme.css b/lib/src/theme.css index 5b2160c4..d7c3b937 100644 --- a/lib/src/theme.css +++ b/lib/src/theme.css @@ -1,4 +1,4 @@ -/* MouseTerm Theme System +/* Dormouse Theme System * * Two-layer CSS variable strategy: * @theme --color-* tokens -> var(--vscode-*) @@ -8,7 +8,7 @@ * * This file intentionally does not provide hardcoded color defaults. Runtime * hosts must provide real VSCode variables, either from VSCode itself or from - * a bundled/installed MouseTerm theme. + * a bundled/installed Dormouse theme. * * Two consumers read --vscode-* variables: * 1. @theme/body bindings below — for UI colors (surfaces, tabs, etc.) diff --git a/website/public/standalone-latest.json b/website/public/standalone-latest.json index 765b9916..56da6f0e 100644 --- a/website/public/standalone-latest.json +++ b/website/public/standalone-latest.json @@ -4,15 +4,15 @@ "pub_date": "2026-05-01T22:02:25Z", "platforms": { "darwin-aarch64": { - "url": "https://github.com/diffplug/dormouse/releases/download/v0.9.1/MouseTerm-macos-aarch64.tar.gz", + "url": "https://github.com/diffplug/dormouse/releases/download/v0.9.1/Dormouse-macos-aarch64.tar.gz", "signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUlVUYlpCcFVqWDVhckMvSXc5aXd0TXNFdEUwcmVxOUpvZG53SmdZQ0ZhYlhDcUQ2WU1LWlF1eGx2bG15NHJEa3phK1ZVcjFIaWVGNGUwS3BqKzFXMmNPUWlmem1wMUJBSGdNPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNzc3NjcyOTQ0CWZpbGU6TW91c2VUZXJtLW1hY29zLWFhcmNoNjQudGFyLmd6ClE3OWlndURTdkI5Mm9FeXM0U1Q4RGxYbFFtU2xIN1RsN095VmhlZTJqQ215U01yRFphNE5jYnNvcTZjUGI5Q0dObHgyMTJieE1pK2ZwdXJFTFZMb0JBPT0K" }, "windows-x86_64": { - "url": "https://github.com/diffplug/dormouse/releases/download/v0.9.1/MouseTerm-windows-x64-setup.exe", + "url": "https://github.com/diffplug/dormouse/releases/download/v0.9.1/Dormouse-windows-x64-setup.exe", "signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUlVUYlpCcFVqWDVhckdQRmYzS1dnRnYzUzBiUUMzanNINFpzLzZTemtwNTM4L3NmOUN0SGxVcEZFcG5yOGxOS1JGRzY1Skw0RGRXenlQMHlmNlB6ZkRLYTNLeE9hWVB0U3dZPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNzc3NjcyOTQ0CWZpbGU6TW91c2VUZXJtLXdpbmRvd3MteDY0LXNldHVwLmV4ZQp5ZU9XbGlSNFFNTVd0NFdZWi9YQW9KU1pxZWtDS3gyYWY4OUJ4eHZ4YkN0UkNicVZ5OXBFakdWWktOcmd6R29LZHRMeHlReCswa3d5VTgvbTNEK0xBQT09Cg==" }, "linux-x86_64": { - "url": "https://github.com/diffplug/dormouse/releases/download/v0.9.1/MouseTerm-linux-x86_64.AppImage", + "url": "https://github.com/diffplug/dormouse/releases/download/v0.9.1/Dormouse-linux-x86_64.AppImage", "signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUlVUYlpCcFVqWDVhckdvR3lCT3ZHQW51b1gxUWVVbGNNVlFMK0ZDNmdKdE5zYURXaHliaDFodHdoOFJabHB4UjVsalB1QWpTQ2NCNmlqOER5bGRpdXlCY0p2VHRCZEVWK3dBPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNzc3NjcyOTQ1CWZpbGU6TW91c2VUZXJtLWxpbnV4LXg4Nl82NC5BcHBJbWFnZQoza240b1AyVWw2T3VKUmhFUXdRanRlNVVaVGRvUUUxL1VqZ3FCTmJrQmpzNmxPVnRpd3BxRzJwN3NyRnB1YmZoUUIxK2E3WG9ZbmlJUnFYK0J6Uk1EZz09Cg==" } } diff --git a/website/src/lib/__snapshots__/tut-runner.test.ts.snap b/website/src/lib/__snapshots__/tut-runner.test.ts.snap index de1bd95c..12530a17 100644 --- a/website/src/lib/__snapshots__/tut-runner.test.ts.snap +++ b/website/src/lib/__snapshots__/tut-runner.test.ts.snap @@ -32,7 +32,7 @@ exports[`TutRunner snapshots > renders Copy paste with all items incomplete 1`] Press p to toggle the Place To Paste. Some terminal programs trap the cursor, and some do not. This tutorial pane - does not trap the cursor, so MouseTerm does not show a cursor icon. The + does not trap the cursor, so Dormouse does not show a cursor icon. The ascii-splash and changelog programs trap the cursor — that is how they are able to respond to mouse movement. lazygit is an excellent and popular program which traps the cursor. @@ -78,7 +78,7 @@ exports[`TutRunner snapshots > renders Keyboard navigation with all items incomp exports[`TutRunner snapshots > renders the top-level menu 1`] = ` " - MouseTerm Playground Tutorial + Dormouse Playground Tutorial 0/17 complete · Esc/q to exit · Enter to open · ↑↓ to navigate ❯ Keyboard navigation [0/7 complete] diff --git a/website/src/lib/ascii-splash-runner.ts b/website/src/lib/ascii-splash-runner.ts index 2e0fde56..55dbe580 100644 --- a/website/src/lib/ascii-splash-runner.ts +++ b/website/src/lib/ascii-splash-runner.ts @@ -1,5 +1,5 @@ /* - * Browser adapter for ascii-splash@0.3.0 in the MouseTerm website playground. + * Browser adapter for ascii-splash@0.3.0 in the Dormouse website playground. * * This file is not the upstream CLI entrypoint. It imports upstream internals * from ascii-splash/dist through the website's `ascii-splash-internal` Vite @@ -14,7 +14,7 @@ * - BrowserTerminalRenderer writes ANSI output through FakePtyAdapter.sendOutput. * - Keyboard bytes and SGR mouse sequences are decoded from writePty input. * - Alt-screen, cursor, mouse-reporting, resize, start, and cleanup lifecycle - * are handled for xterm.js inside MouseTerm. + * are handled for xterm.js inside Dormouse. * - UI overlays/transitions are instantiated per runner instead of using * upstream singleton getters so multiple panes can run independently. * - Config persistence is intentionally omitted; upstream commands that need a diff --git a/website/src/lib/changelog-runner.ts b/website/src/lib/changelog-runner.ts index 622b0488..e598cba3 100644 --- a/website/src/lib/changelog-runner.ts +++ b/website/src/lib/changelog-runner.ts @@ -307,7 +307,7 @@ export class ChangelogRunner implements InteractiveProgram { const detailLines = this.getDetailLines(); let frame = `${CURSOR_HOME}${CLEAR_SCREEN}`; - frame += `${BOLD}MouseTerm changelog${RESET} ${DIM}${RELEASES.length} releases · \`q\` to quit · ↑↓ select · wheel scrolls${RESET}\r\n`; + frame += `${BOLD}Dormouse changelog${RESET} ${DIM}${RELEASES.length} releases · \`q\` to quit · ↑↓ select · wheel scrolls${RESET}\r\n`; frame += "\r\n"; for (let r = 0; r < bodyH; r++) { diff --git a/website/src/lib/tut-items.ts b/website/src/lib/tut-items.ts index 4d060433..a1bc36fb 100644 --- a/website/src/lib/tut-items.ts +++ b/website/src/lib/tut-items.ts @@ -99,7 +99,7 @@ export const SECTIONS: readonly Section[] = [ id: 'al-ring', title: 'Bell rings when the task completes', hint: - `Don't type! If you type, MouseTerm will think you are paying attention to this task and the bell will not ring. The bell only rings if (a) the pane is not selected or (b) you have not interacted with the pane for the past ${USER_ATTENTION_SECS} seconds.`, + `Don't type! If you type, Dormouse will think you are paying attention to this task and the bell will not ring. The bell only rings if (a) the pane is not selected or (b) you have not interacted with the pane for the past ${USER_ATTENTION_SECS} seconds.`, }, { id: 'al-todo-auto', @@ -145,7 +145,7 @@ export const SECTIONS: readonly Section[] = [ }, ], prose: [ - 'Some terminal programs trap the cursor, and some do not. This tutorial pane does not trap the cursor, so MouseTerm does not show a cursor icon. The `ascii-splash` and `changelog` programs trap the cursor — that is how they are able to respond to mouse movement. `lazygit` is an excellent and popular program which traps the cursor.', + 'Some terminal programs trap the cursor, and some do not. This tutorial pane does not trap the cursor, so Dormouse does not show a cursor icon. The `ascii-splash` and `changelog` programs trap the cursor — that is how they are able to respond to mouse movement. `lazygit` is an excellent and popular program which traps the cursor.', ], }, ]; diff --git a/website/src/lib/tut-runner.test.ts b/website/src/lib/tut-runner.test.ts index 2a4178ca..7f583530 100644 --- a/website/src/lib/tut-runner.test.ts +++ b/website/src/lib/tut-runner.test.ts @@ -83,7 +83,7 @@ describe("TutRunner snapshots", () => { sendKeys("\r"); sendKeys("q"); - expect(lastFrame()).toContain("MouseTerm Playground Tutorial"); + expect(lastFrame()).toContain("Dormouse Playground Tutorial"); expect(exitCount()).toBe(0); sendKeys("q"); diff --git a/website/src/lib/tut-runner.ts b/website/src/lib/tut-runner.ts index e228fd1b..aef9e240 100644 --- a/website/src/lib/tut-runner.ts +++ b/website/src/lib/tut-runner.ts @@ -319,7 +319,7 @@ export class TutRunner implements InteractiveProgram { const total = this.state.totalProgress(); const lines: string[] = []; lines.push(""); - lines.push(` ${BOLD}MouseTerm Playground Tutorial${RESET}`); + lines.push(` ${BOLD}Dormouse Playground Tutorial${RESET}`); lines.push( ` ${DIM}${total.done}/${total.total} complete · \`Esc\`/\`q\` to exit · \`Enter\` to open · \`↑↓\` to navigate${RESET}`, ); diff --git a/website/src/pages/Changelog.tsx b/website/src/pages/Changelog.tsx index 91b1d408..1e5fb39d 100644 --- a/website/src/pages/Changelog.tsx +++ b/website/src/pages/Changelog.tsx @@ -183,7 +183,7 @@ export function Component() { Changelog

- Release notes for MouseTerm. + Release notes for Dormouse.

From e50765ae9731ce7abf7c4cc1bee105996a0d4974 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Sun, 17 May 2026 22:13:18 -0700 Subject: [PATCH 14/24] Re-time the hero: 3-line hook with revealed dormouse line, restore original word reveal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hook now contains three lines instead of two: 1. "So many busy terminals." (visible on load) 2. "Which needs attention?" (visible on load) 3. "A dormouse knows when to wake." (revealed during icon climb) Line 3 fades in via a new scroll-driven span (dormouseLineRef) tied to the icon-rise progress. New constants control the timing: DORMOUSE_LINE_REVEAL_START = 0.25 // starts fading in at 25% climb DORMOUSE_LINE_REVEAL_END = 0.55 // fully visible at 55% climb Once the climb finishes, the existing HOOK_FADE_REMAINING fade drops the whole 3-line block at once. The word reveal returns to the original copy, timing, and size: - "Multitasking" / "terminal" / "for mice (caramel)" - footnote: "(and hotkey wizards too)" - size: text-[clamp(2.5rem,5vw+0.5rem,4rem)] - WORD_THRESHOLDS [0.25, 0.40, 0.55] unchanged Hook size stays at the smaller clamp(2rem,…,3.5rem) since line 3 is longer than the original two-line hook, but the reveal is now back to the larger original clamp. Co-Authored-By: Claude Opus 4.7 (1M context) --- website/src/pages/Home.tsx | 47 ++++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/website/src/pages/Home.tsx b/website/src/pages/Home.tsx index b02bdb31..9771855d 100644 --- a/website/src/pages/Home.tsx +++ b/website/src/pages/Home.tsx @@ -38,6 +38,11 @@ const HOOK_FADE_REMAINING = 0.10; // Hook begins fading when bottom 10% of ic const WORD_THRESHOLDS = [0.25, 0.40, 0.55] as const; const FOOTNOTE_THRESHOLD = 0.65; const HEADER_REVEAL_LEAD = 0.04; +/** Fractions of the icon rise during which the 3rd hook line ("A dormouse knows…") + * fades in. Lines 1+2 of the hook are visible at load; line 3 is revealed + * partway through the climb, before the whole hook fades out. */ +const DORMOUSE_LINE_REVEAL_START = 0.25; +const DORMOUSE_LINE_REVEAL_END = 0.55; /** Fraction of runway where the hero text unpins and scrolls away (0–1). * The video keeps scrubbing underneath. */ @@ -272,6 +277,7 @@ function Home() { const headerRef = useRef(null); const headerBrandRef = useRef(null); const hookRef = useRef(null); + const dormouseLineRef = useRef(null); const contentRef = useRef(null); const [installGuide, setInstallGuide] = useState(null); const [heroVideoSrc, setHeroVideoSrc] = useState(); @@ -546,6 +552,20 @@ function Home() { hookRef.current.style.transform = `translateY(${-fadeProgress * 24}px)`; } + // Third hook line ("A dormouse knows when to wake.") fades in partway + // through the icon climb so it lands before the whole hook fades out. + if (dormouseLineRef.current) { + const iconRiseProgress = iconHidePx > 0 + ? clamp01(1 - iconCurrentOffset / iconHidePx) + : 0; + const dormouseProgress = clamp01( + (iconRiseProgress - DORMOUSE_LINE_REVEAL_START) + / (DORMOUSE_LINE_REVEAL_END - DORMOUSE_LINE_REVEAL_START) + ); + dormouseLineRef.current.style.opacity = String(dormouseProgress); + dormouseLineRef.current.style.transform = `translateY(${(1 - dormouseProgress) * 8}px)`; + } + // Hero: cap so it stops at unstick (fraction = 1); natural scroll takes over. const maxHeroOffset = runway.offsetHeight * (1 - UNPIN_THRESHOLD); const heroOffset = Math.min(slideAmount, maxHeroOffset); @@ -704,31 +724,40 @@ function Home() { {/* ── Pinned scroll runway: hero text overlay ── */}
- {/* Hook copy — visible on load, fades out on first scroll */} + {/* Hook copy — lines 1+2 visible on load; line 3 reveals partway + through the icon climb; the whole block fades out as the icon + nears the top. */}
- So many terminals. - Which one needs attention? + So many busy terminals. + Which needs attention? + + A dormouse knows when to wake. +
{/* Hero words — crossfade in place with the hook, just below the header */} -
+
- A dormouse + Multitasking - knows when + terminal - to wake up. + for mice

- Multitasking terminal for mice (and hotkey wizards too) + (and hotkey wizards too)

From d85d48ec5b8fc66dd85954f47436341c1381db9e Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Sun, 17 May 2026 22:36:10 -0700 Subject: [PATCH 15/24] Fix mobile spacing in the hero hook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit At display sizes (clamp 2-3.5rem) with default line-height (~1.4), wrapped text inside a single opens a gap roughly equal to gap-2 between sibling s. The two distances become indistinguishable, so the eye can't tell which lines belong together. - leading-tight (1.25) on the hook container so wrapped text in a single line hugs together (~8px wrap gap at 32px mobile font) - gap-2 → gap-3 so the inter-span gap (12px) is clearly larger than the wrap gap, restoring the 1.5× hierarchy - Drop the dormouse line's mt-2: the opacity reveal already signals "new line"; uniform rhythm reads cleaner than a manual nudge Picks up the live-edited hook copy (lines 1–2 with opacity-80 to weight attention toward the dormouse reveal, "Which ones need attention?"). Dormouse line stays at "A dormouse knows when to wake." — the product alerts/wakes you; it doesn't put anything to sleep, so the verb pair "sleep + wake" would overclaim. Co-Authored-By: Claude Opus 4.7 (1M context) --- website/src/pages/Home.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/website/src/pages/Home.tsx b/website/src/pages/Home.tsx index 9771855d..bb3fe171 100644 --- a/website/src/pages/Home.tsx +++ b/website/src/pages/Home.tsx @@ -729,13 +729,12 @@ function Home() { nears the top. */}
- So many busy terminals. - Which needs attention? + So many terminals. + Which ones need attention? A dormouse knows when to wake. From 3276a4445b9b06a8415d6861b55edf7f6ef52c9e Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Sun, 17 May 2026 22:39:53 -0700 Subject: [PATCH 16/24] Hold the dormouse line on stage after lines 1+2 fade MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously the third hook line lived inside hookRef, so when the hook container faded out (during the icon's last 10% climb) all three lines went together. With the new structure, lines 1+2 sit inside hookRef and the dormouse line is a sibling at the layout level — its opacity is driven independently. Timeline: - icon climb 25% → 55%: dormouse line fades in - icon's last 10% : hookRef (lines 1+2) fades up and out - dormouse holds at 1.0 through the gap - runway fraction 0.17 → 0.25: dormouse fades out and translates up - runway fraction 0.25: "Multitasking" begins to pop in (unchanged) The fade-out range ends exactly at WORD_THRESHOLDS[0] so the dormouse is gone the instant the word reveal starts — handing the stage off cleanly. Added two new constants (DORMOUSE_LINE_FADE_OUT_START / END) and a will-change-transform on both fading layers so the compositor keeps their layers warm during the animation. Co-Authored-By: Claude Opus 4.7 (1M context) --- website/src/pages/Home.tsx | 44 +++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/website/src/pages/Home.tsx b/website/src/pages/Home.tsx index bb3fe171..2baf0aac 100644 --- a/website/src/pages/Home.tsx +++ b/website/src/pages/Home.tsx @@ -40,9 +40,14 @@ const FOOTNOTE_THRESHOLD = 0.65; const HEADER_REVEAL_LEAD = 0.04; /** Fractions of the icon rise during which the 3rd hook line ("A dormouse knows…") * fades in. Lines 1+2 of the hook are visible at load; line 3 is revealed - * partway through the climb, before the whole hook fades out. */ + * partway through the climb. */ const DORMOUSE_LINE_REVEAL_START = 0.25; const DORMOUSE_LINE_REVEAL_END = 0.55; +/** Runway fractions over which the dormouse line fades out. Lines 1+2 have + * already dropped (with the icon's last 10% climb); the dormouse line keeps + * carrying the brand alone until "Multitasking" pops in at WORD_THRESHOLDS[0]. */ +const DORMOUSE_LINE_FADE_OUT_START = 0.17; +const DORMOUSE_LINE_FADE_OUT_END = WORD_THRESHOLDS[0]; /** Fraction of runway where the hero text unpins and scrolls away (0–1). * The video keeps scrubbing underneath. */ @@ -552,18 +557,26 @@ function Home() { hookRef.current.style.transform = `translateY(${-fadeProgress * 24}px)`; } - // Third hook line ("A dormouse knows when to wake.") fades in partway - // through the icon climb so it lands before the whole hook fades out. + // Dormouse line ("A dormouse knows when to wake."): fades in partway + // through the icon climb, holds while lines 1+2 fade out and the icon + // settles, then fades out just before "Multitasking" pops in. if (dormouseLineRef.current) { const iconRiseProgress = iconHidePx > 0 ? clamp01(1 - iconCurrentOffset / iconHidePx) : 0; - const dormouseProgress = clamp01( + const fadeIn = clamp01( (iconRiseProgress - DORMOUSE_LINE_REVEAL_START) / (DORMOUSE_LINE_REVEAL_END - DORMOUSE_LINE_REVEAL_START) ); - dormouseLineRef.current.style.opacity = String(dormouseProgress); - dormouseLineRef.current.style.transform = `translateY(${(1 - dormouseProgress) * 8}px)`; + const fadeOut = clamp01( + (fraction - DORMOUSE_LINE_FADE_OUT_START) + / (DORMOUSE_LINE_FADE_OUT_END - DORMOUSE_LINE_FADE_OUT_START) + ); + dormouseLineRef.current.style.opacity = String(fadeIn * (1 - fadeOut)); + // (1-fadeIn) lifts it into place from below as it appears; fadeOut + // lifts it further out as it leaves, matching the hook's exit motion. + const translateY = (1 - fadeIn) * 8 + fadeOut * -20; + dormouseLineRef.current.style.transform = `translateY(${translateY.toFixed(2)}px)`; } // Hero: cap so it stops at unstick (fraction = 1); natural scroll takes over. @@ -724,17 +737,18 @@ function Home() { {/* ── Pinned scroll runway: hero text overlay ── */}
- {/* Hook copy — lines 1+2 visible on load; line 3 reveals partway - through the icon climb; the whole block fades out as the icon - nears the top. */} -
- So many terminals. - Which ones need attention? + {/* Hook copy — lines 1+2 visible on load and fade out as the icon + nears the top. The dormouse line is a sibling, not a child of + hookRef, so its opacity is driven independently and it can + linger after lines 1+2 are gone. */} +
+
+ So many terminals. + Which ones need attention? +
A dormouse knows when to wake. From 9c4f0798680144ea512a4f0cc9f5dd6759c666ef Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Sun, 17 May 2026 22:57:54 -0700 Subject: [PATCH 17/24] Snap fractional hero opacity to crisp 0/1 endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The scroll smoother (HERO_SCROLL_HALFLIFE_S = 0.06) settles asymptotically near each threshold. Opacity values like 0.004 leave a ghost of the element rendered: visually a "hair off transparent", and worse, any non-zero opacity disables the browser's `opacity: 0` compositor fast-path so the ghost is sometimes painted and sometimes not — the inconsistency that read as a race condition on scroll-up. Add a `snapProgress(p, eps = 0.005)` helper that rounds 0..1 values within eps of an endpoint to exactly 0 or 1, and apply it everywhere syncScrollState writes opacity: - the three word reveals (Multitasking / terminal / for mice) - the footnote - the header brand fade - the hook (lines 1+2) fade-out - the dormouse line's combined fade-in × fade-out Transforms are left raw — sub-pixel translateY drift is imperceptible and not subject to the same compositor edge case. The header backgroundColor/blur both scale off the snapped headerProgress, so they also lock to clean 0 at the bottom of their range. Co-Authored-By: Claude Opus 4.7 (1M context) --- website/src/pages/Home.tsx | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/website/src/pages/Home.tsx b/website/src/pages/Home.tsx index 2baf0aac..57f8de31 100644 --- a/website/src/pages/Home.tsx +++ b/website/src/pages/Home.tsx @@ -66,6 +66,12 @@ const SECTION_PY = "py-8"; /** Clamp a value to 0–1. */ const clamp01 = (v: number) => Math.min(1, Math.max(0, v)); +/** Snap a 0..1 progress to crisp 0/1 endpoints when within `eps`. The scroll + * smoother settles asymptotically, so opacity values can land at e.g. 0.004 + * near a threshold — visually a ghost, and worse, it disables the browser's + * `opacity: 0` compositor fast-path. Snapping makes the endpoints exact. */ +const snapProgress = (p: number, eps = 0.005): number => + p < eps ? 0 : p > 1 - eps ? 1 : p; const useClientLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect; const downloadAccentStyle = { @@ -508,21 +514,21 @@ function Home() { const progress = clamp01( (fraction - WORD_THRESHOLDS[i]) / 0.08 ); - el.style.opacity = String(progress); + el.style.opacity = String(snapProgress(progress)); el.style.transform = `translateY(${(1 - progress) * 12}px)`; } // Footnote - const footnoteProgress = clamp01( + const footnoteProgress = snapProgress(clamp01( (fraction - FOOTNOTE_THRESHOLD) / 0.08 - ); + )); if (footnoteRef.current) footnoteRef.current.style.opacity = String(footnoteProgress * 0.7); // Header: reveal brand + background just before the tmux-shortcuts // footnote appears, so it reads as dark once the line is visible. - const headerProgress = clamp01( + const headerProgress = snapProgress(clamp01( (fraction - (FOOTNOTE_THRESHOLD - HEADER_REVEAL_LEAD)) / HEADER_REVEAL_LEAD - ); + )); if (headerBrandRef.current) { headerBrandRef.current.style.opacity = String(headerProgress); } @@ -553,7 +559,7 @@ function Home() { const fadeProgress = iconCurrentOffset === 0 ? 1 : clamp01(1 - remainingHidden / HOOK_FADE_REMAINING); - hookRef.current.style.opacity = String(1 - fadeProgress); + hookRef.current.style.opacity = String(snapProgress(1 - fadeProgress)); hookRef.current.style.transform = `translateY(${-fadeProgress * 24}px)`; } @@ -572,7 +578,7 @@ function Home() { (fraction - DORMOUSE_LINE_FADE_OUT_START) / (DORMOUSE_LINE_FADE_OUT_END - DORMOUSE_LINE_FADE_OUT_START) ); - dormouseLineRef.current.style.opacity = String(fadeIn * (1 - fadeOut)); + dormouseLineRef.current.style.opacity = String(snapProgress(fadeIn * (1 - fadeOut))); // (1-fadeIn) lifts it into place from below as it appears; fadeOut // lifts it further out as it leaves, matching the hook's exit motion. const translateY = (1 - fadeIn) * 8 + fadeOut * -20; From be011947a418b4d50f06c6adec75046bbf981f91 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Mon, 18 May 2026 12:36:37 -0700 Subject: [PATCH 18/24] Enable kitty keyboard protocol so Shift+Enter is distinguishable In a stock xterm.js terminal, Shift+Enter and Enter both send `\r`, so TUIs like Claude Code can't bind them differently and both submit. Upgrading @xterm/xterm to a 6.1.0 beta and opting into `vtExtensions.kittyKeyboard` lets the terminal advertise the kitty keyboard protocol; when the inner program negotiates it, modified keys round-trip as distinct CSI u sequences. Co-Authored-By: Claude Opus 4.7 --- lib/package.json | 4 ++-- lib/src/lib/terminal-lifecycle.ts | 1 + pnpm-lock.yaml | 32 +++++++++++++++++-------------- standalone/package.json | 4 ++-- 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/lib/package.json b/lib/package.json index c0b94091..4d071773 100644 --- a/lib/package.json +++ b/lib/package.json @@ -15,8 +15,8 @@ }, "dependencies": { "@phosphor-icons/react": "^2.1.10", - "@xterm/addon-fit": "^0.11.0", - "@xterm/xterm": "^6.0.0", + "@xterm/addon-fit": "0.12.0-beta.216", + "@xterm/xterm": "6.1.0-beta.216", "clsx": "^2.1.1", "dockview-react": "^5.1.0", "react": "^19.2.0", diff --git a/lib/src/lib/terminal-lifecycle.ts b/lib/src/lib/terminal-lifecycle.ts index 909c8bb9..6db3c174 100644 --- a/lib/src/lib/terminal-lifecycle.ts +++ b/lib/src/lib/terminal-lifecycle.ts @@ -54,6 +54,7 @@ function createXtermHost(): { terminal: Terminal; fit: FitAddon; element: HTMLDi fontFamily: editorFontFamily, cursorBlink: true, theme, + vtExtensions: { kittyKeyboard: true }, }); const fit = new FitAddon(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f5acbc51..f46f02fe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,11 +14,11 @@ importers: specifier: ^2.1.10 version: 2.1.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@xterm/addon-fit': - specifier: ^0.11.0 - version: 0.11.0 + specifier: 0.12.0-beta.216 + version: 0.12.0-beta.216(@xterm/xterm@6.1.0-beta.216) '@xterm/xterm': - specifier: ^6.0.0 - version: 6.0.0 + specifier: 6.1.0-beta.216 + version: 6.1.0-beta.216 clsx: specifier: ^2.1.1 version: 2.1.1 @@ -96,11 +96,11 @@ importers: specifier: ^2.10.1 version: 2.10.1 '@xterm/addon-fit': - specifier: ^0.11.0 - version: 0.11.0 + specifier: 0.12.0-beta.216 + version: 0.12.0-beta.216(@xterm/xterm@6.1.0-beta.216) '@xterm/xterm': - specifier: ^6.0.0 - version: 6.0.0 + specifier: 6.1.0-beta.216 + version: 6.1.0-beta.216 dockview-react: specifier: ^5.1.0 version: 5.1.0(react@19.2.4) @@ -1640,11 +1640,13 @@ packages: engines: {node: '>= 20'} hasBin: true - '@xterm/addon-fit@0.11.0': - resolution: {integrity: sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g==} + '@xterm/addon-fit@0.12.0-beta.216': + resolution: {integrity: sha512-IgKE3ngNodSnmj1O+EEYpKQZkSbAUbghPlCWd8G32RL0piIMqb3FX3BuYLnWZeLNoD9iMtublLMG1T9XjGeVvA==} + peerDependencies: + '@xterm/xterm': ^6.1.0-beta.216 - '@xterm/xterm@6.0.0': - resolution: {integrity: sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg==} + '@xterm/xterm@6.1.0-beta.216': + resolution: {integrity: sha512-87rfymzVje5eYUlGG94hz1WkOYvFRcFDGdiOAbg4d8xt8OGSGR2nMNU4I1n5MDE1RBPBqRd+WVJ5w7q3pwMoZA==} acorn@8.16.0: resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} @@ -4845,9 +4847,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@xterm/addon-fit@0.11.0': {} + '@xterm/addon-fit@0.12.0-beta.216(@xterm/xterm@6.1.0-beta.216)': + dependencies: + '@xterm/xterm': 6.1.0-beta.216 - '@xterm/xterm@6.0.0': {} + '@xterm/xterm@6.1.0-beta.216': {} acorn@8.16.0: {} diff --git a/standalone/package.json b/standalone/package.json index 5f1731e1..13875d95 100644 --- a/standalone/package.json +++ b/standalone/package.json @@ -15,8 +15,8 @@ "@tauri-apps/api": "^2.0.0", "@tauri-apps/plugin-shell": "^2.0.0", "@tauri-apps/plugin-updater": "^2.10.1", - "@xterm/addon-fit": "^0.11.0", - "@xterm/xterm": "^6.0.0", + "@xterm/addon-fit": "0.12.0-beta.216", + "@xterm/xterm": "6.1.0-beta.216", "dockview-react": "^5.1.0", "dormouse-lib": "workspace:*", "react": "^19.0.0", From 6265d0e71afb3a25cbf1315b71938771340de6b7 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Mon, 18 May 2026 13:10:19 -0700 Subject: [PATCH 19/24] Sync Cargo.lock with renamed `dormouse` crate Cargo.toml's [package].name became `dormouse` in the earlier rebrand commit. Running `cargo check` regenerates the lockfile's package entry; commit that so CI's `--frozen-lockfile` doesn't trip. Co-Authored-By: Claude Opus 4.7 (1M context) --- standalone/src-tauri/Cargo.lock | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/standalone/src-tauri/Cargo.lock b/standalone/src-tauri/Cargo.lock index 4fa405df..c7247f68 100644 --- a/standalone/src-tauri/Cargo.lock +++ b/standalone/src-tauri/Cargo.lock @@ -667,6 +667,20 @@ dependencies = [ "tendril 0.5.0", ] +[[package]] +name = "dormouse" +version = "0.9.1" +dependencies = [ + "process-wrap", + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-shell", + "tauri-plugin-updater", + "windows 0.62.2", +] + [[package]] name = "dpi" version = "0.1.2" @@ -1942,20 +1956,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "dormouse" -version = "0.9.1" -dependencies = [ - "process-wrap", - "serde", - "serde_json", - "tauri", - "tauri-build", - "tauri-plugin-shell", - "tauri-plugin-updater", - "windows 0.62.2", -] - [[package]] name = "muda" version = "0.17.1" From a286679a462344cb87c4e95681e0aa4bebde1b2e Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Mon, 18 May 2026 13:10:37 -0700 Subject: [PATCH 20/24] Rework homepage marketing: reorder sections, crossfade dormouse line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Home.md (new): canonical marketing-copy reference for the homepage. Native markdown — `##` headings then prose, no metadata labels — so it reads as the actual marketing voice. Keep in sync with Home.tsx when copy changes. README.md: swap the inline hero image to website/public/og-image.jpg (the 3D dormouse-and-terminal social card), restructure the Try-it links into Playground / VS Code marketplace / Standalone, and trim the lead paragraphs. Home.tsx — feature sections: - Reorder to match Home.md: tmux first, then alerts, then copy-paste. - Section 1 "Soft as a mouse, sharp as a tmux" (was last): leads with the big clamp(1.5rem,2.5vw+0.5rem,2.25rem) heading inside a `max-w-2xl` text well, sitting over a `max-w-5xl` full-width tmux video. New body copy: "Upgrade your VS Code or native terminal with a flexible multipane layout. Minimize the tasks you're not watching to a compact status indicator." - Section 2 "Stop watching terminals spin" (was first): two-column `max-w-5xl grid-cols-[3fr_2fr]` with alert video on the left. Append BEL / OSC 9/99/777 support to the second paragraph. - Section 3 "Newlines and copy paste like you meant" (was middle, retitled): two-column `grid-cols-[2fr_3fr]`. New opening paragraph about Shift+Enter; drop the trailing "Dormouse lets you copy paste like a human" line. Home.tsx — hero animation: - Pull hookFadeProgress out of the hookRef-only block so the dormouse line can share it. Dormouse fadeIn = hookFadeProgress: as lines 1+2 fade out the dormouse fades in by the exact same amount. Perfect crossfade instead of the earlier mid-climb reveal. - Drop the now-unused DORMOUSE_LINE_REVEAL_START / END constants; keep DORMOUSE_LINE_FADE_OUT_* for the later handoff to "Multitasking". Note: README.md line 11 has an in-flight phrase that may need a follow-up edit ("with standard terminal .,mn,.mn.,mn"). Co-Authored-By: Claude Opus 4.7 (1M context) --- Home.md | 77 +++++++++++++++++++++++ README.md | 19 ++---- website/src/pages/Home.tsx | 123 +++++++++++++++++++------------------ 3 files changed, 145 insertions(+), 74 deletions(-) create mode 100644 Home.md diff --git a/Home.md b/Home.md new file mode 100644 index 00000000..d02a63b8 --- /dev/null +++ b/Home.md @@ -0,0 +1,77 @@ +# Dormouse + +So many terminals. +Which ones need attention? +A *dormouse* knows when to wake. + +Multitasking terminal for mice (and hotkey wizards too) + +## Soft as a mouse, sharp as a tmux + +Upgrade your VS Code or native terminal with a flexible multipane layout. Minimize the tasks you're not watching to a compact status indicator. + +Do it all with the mouse, or keep your hands on the keyboard with tmux keybinds. + +## Stop watching terminals spin + +Dormouse tracks activity the same way you do — visual motion. When a pane stops changing for two seconds, it marks the task complete and alerts you. + +Works with any CLI tool that prints to a terminal — no plugins, no configuration. Also supports `BEL` and `OSC 9/99/777` for seamless integration with TUI-forward applications. + +## Newlines and copy paste like you meant + +You're used to doing `Shift+Enter` to get a newline in the browser, but it's broken in your terminal? Not anymore. Dormouse works the way a user would expect, no arcane terminal knowledge required! + +Click and drag in a "mouse conformant" terminal doesn't select text; it sends escape code `\e[<0;x;yM`. And `Ctrl+C` doesn't copy; it asks your program to kill itself. + +## Get Dormouse + +A dormouse knows when to wake up. Multitasking terminal for mice. + +[Try it in the Playground](/playground) + +### VS Code Extension + +Also works in Cursor, Windsurf, Antigravity, or any other VS Code fork. + +- [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=diffplug.dormouse) +- [Open VSX Registry](https://open-vsx.org/extension/diffplug/dormouse) + +### Standalone App + +Don't settle for your operating system's built-in terminal, get a nice one! + +- Mac Silicon +- Windows x64 +- Linux x64 +- [Other](https://github.com/diffplug/dormouse/issues/8) + +#### Installing on Mac + +1. Double-click the downloaded .tar.gz to extract Dormouse.app +2. Drag Dormouse.app to Applications + +#### Installing on Windows + +1. Double-click the downloaded Dormouse-windows-x64-setup.exe +2. If SmartScreen appears: More info → Run anyway + +#### Installing on Linux + +1. Make executable: `chmod +x Dormouse-linux-x86_64.AppImage` +2. Run from terminal or double-click to launch + +## Walk away. Keep going. + +Coming next: [Dormouse Pocket](/pocket). Tether a terminal session to your phone over WebRTC and take a stroll — the Dormouse alert system buzzes you if there's anything to do. A hosted auto-pairing service comes later, so you can just leave and keep working, no "I'm walking away" dance. + +Open source and free to self-host, or pay us a little bit and you can use ours. We'll discount for early adopters, so don't miss out! + +**Notify me when Pocket ships.** This signs you up for my personal devlog [nedshed.dev](https://nedshed.dev) on Substack. The next post will be the launch post, you can unsubscribe any time. + +--- + +Built by [nedshed.dev](https://nedshed.dev) (the labs division of [DiffPlug LLC](https://diffplug.com)). + +- [Dependencies](/dependencies) +- [Report an issue](https://github.com/diffplug/dormouse/issues) diff --git a/README.md b/README.md index 9704cd4d..c4f5ccc4 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,14 @@ -# Dormouse - -**A dormouse knows when to wake up. Multitasking terminal for mice (and hotkey wizards too).** - -So many terminals — which one needs attention? Dormouse tracks activity the -way you do: visual motion. When a pane stops changing for two seconds, -it's marked done. Works with any CLI tool that prints to a terminal — -no plugins, no configuration. - -![Dormouse hero](website/src/assets/video-climb-blink-and-stare.webp) +![Dormouse — Multitasking Terminal for Mice](website/public/og-image.jpg) ## Try it -- **[Playground](https://dormouse.sh/playground)** — try in your browser, no install -- **[Demo videos and downloads](https://dormouse.sh)** — Mac, Windows, Linux -- **[Marketplace](https://marketplace.visualstudio.com/items?itemName=diffplug.dormouse)** / **[Open VSX](https://open-vsx.org/extension/diffplug/dormouse)** — VS Code extension (also works in Cursor, Windsurf, Antigravity) +- **[Playground](https://dormouse.sh/playground)** - try in your browser, no install +- **[VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=diffplug.dormouse)** / **[Open VSX](https://open-vsx.org/extension/diffplug/dormouse)** - works in VS Code and its forks +- **[Standalone app](https://dormouse.sh#downloads)** - Mac, Windows, Linux ## Features -- **Automatic completion detection.** When a pane goes quiet for two seconds, it's marked done. Works with builds, AI agents, scripts, anything. +- **Automatic completion detection.** Detect when an agent needs your attention with standard terminal .,mn,.mn.,mnWhen a pane goes quiet for two seconds, it's marked done. Works with builds, AI agents, scripts, anything. - **tmux-compatible keybindings.** Same prefix, same splits, same pane navigation. Muscle memory transfers. - **Full mouse support.** Click to split, drag to resize, scroll to navigate. Or stay on the keyboard. - **Copy-paste that works.** Click and drag selects text the way you'd expect, even in mouse-aware TUIs that normally swallow it as escape codes. Ctrl+C copies; killing the program is a separate gesture. diff --git a/website/src/pages/Home.tsx b/website/src/pages/Home.tsx index 57f8de31..b6515570 100644 --- a/website/src/pages/Home.tsx +++ b/website/src/pages/Home.tsx @@ -38,14 +38,10 @@ const HOOK_FADE_REMAINING = 0.10; // Hook begins fading when bottom 10% of ic const WORD_THRESHOLDS = [0.25, 0.40, 0.55] as const; const FOOTNOTE_THRESHOLD = 0.65; const HEADER_REVEAL_LEAD = 0.04; -/** Fractions of the icon rise during which the 3rd hook line ("A dormouse knows…") - * fades in. Lines 1+2 of the hook are visible at load; line 3 is revealed - * partway through the climb. */ -const DORMOUSE_LINE_REVEAL_START = 0.25; -const DORMOUSE_LINE_REVEAL_END = 0.55; -/** Runway fractions over which the dormouse line fades out. Lines 1+2 have - * already dropped (with the icon's last 10% climb); the dormouse line keeps - * carrying the brand alone until "Multitasking" pops in at WORD_THRESHOLDS[0]. */ +/** Runway fractions over which the dormouse line fades out. The dormouse + * line fades IN crossfaded with lines 1+2 (shared HOOK_FADE_REMAINING), + * then keeps carrying the brand alone until "Multitasking" pops in at + * WORD_THRESHOLDS[0] — this range governs that final exit. */ const DORMOUSE_LINE_FADE_OUT_START = 0.17; const DORMOUSE_LINE_FADE_OUT_END = WORD_THRESHOLDS[0]; @@ -554,26 +550,23 @@ function Home() { poster.style.transform = mediaTransform; // Hook text: visible until the icon nearly finishes rising, then fades out. + // hookFadeProgress: 0 = fully visible, 1 = fully gone. Tied to the + // icon's last `HOOK_FADE_REMAINING` of climb. Shared with the dormouse + // line so its fade-in crossfades exactly with this fade-out. + const remainingHidden = iconHeight > 0 ? iconCurrentOffset / iconHeight : 0; + const hookFadeProgress = iconCurrentOffset === 0 + ? 1 + : clamp01(1 - remainingHidden / HOOK_FADE_REMAINING); if (hookRef.current) { - const remainingHidden = iconHeight > 0 ? iconCurrentOffset / iconHeight : 0; - const fadeProgress = iconCurrentOffset === 0 - ? 1 - : clamp01(1 - remainingHidden / HOOK_FADE_REMAINING); - hookRef.current.style.opacity = String(snapProgress(1 - fadeProgress)); - hookRef.current.style.transform = `translateY(${-fadeProgress * 24}px)`; + hookRef.current.style.opacity = String(snapProgress(1 - hookFadeProgress)); + hookRef.current.style.transform = `translateY(${-hookFadeProgress * 24}px)`; } - // Dormouse line ("A dormouse knows when to wake."): fades in partway - // through the icon climb, holds while lines 1+2 fade out and the icon - // settles, then fades out just before "Multitasking" pops in. + // Dormouse line ("A dormouse knows when to wake."): fades in exactly as + // lines 1+2 are leaving (shared hookFadeProgress = a true crossfade), + // holds while the icon settles, then fades out before "Multitasking" pops in. if (dormouseLineRef.current) { - const iconRiseProgress = iconHidePx > 0 - ? clamp01(1 - iconCurrentOffset / iconHidePx) - : 0; - const fadeIn = clamp01( - (iconRiseProgress - DORMOUSE_LINE_REVEAL_START) - / (DORMOUSE_LINE_REVEAL_END - DORMOUSE_LINE_REVEAL_START) - ); + const fadeIn = hookFadeProgress; const fadeOut = clamp01( (fraction - DORMOUSE_LINE_FADE_OUT_START) / (DORMOUSE_LINE_FADE_OUT_END - DORMOUSE_LINE_FADE_OUT_START) @@ -749,8 +742,8 @@ function Home() { linger after lines 1+2 are gone. */}
- So many terminals. - Which ones need attention? + So many terminals. + Which ones need attention?
-
-

Stop watching terminals spin

-

- Dormouse tracks activity the same way you do — visual motion. When a - pane stops changing for two seconds, it marks the task complete and - alerts you. -

-

- Works with any CLI tool that prints to a terminal — no plugins, no - configuration. -

- + {/* Section 1: narrow text over a full-width video — lead with the tmux story */} +
+
+

Soft as a mouse, sharp as a tmux

+

+ Upgrade your VS Code or native terminal with a flexible multipane + layout. Minimize the tasks you're not watching to a compact status + indicator. +

+

+ Do it all with the mouse, or keep your hands on the keyboard with + tmux keybinds. +

+
+
- {/* Section 2: text left, image right */} -
-
-

Copy paste like you meant

+ {/* Section 2: image left, text right */} +
+ +
+

Stop watching terminals spin

- Click and drag in a "mouse conformant" terminal doesn't select text; - it sends escape code{" "} - {"\\e[<0;x;yM"}. - And Ctrl+C{" "} - doesn't copy; it asks your program to kill itself. + Dormouse tracks activity the same way you do — visual motion. When a + pane stops changing for two seconds, it marks the task complete and + alerts you.

- Dormouse lets you copy paste like a human, not a terminal. + Works with any CLI tool that prints to a terminal — no plugins, no + configuration. Also supports{" "} + BEL{" "} + and{" "} + OSC 9/99/777{" "} + for seamless integration with TUI-forward applications.

-
- {/* Section 3: image left, text right */} -
- -
-

Soft as a mouse, sharp as tmux

+ {/* Section 3: text left, image right */} +
+
+

Newlines and copy paste like you meant

- Run builds, agents, servers, and scripts side by side. Minimize the - ones you're not watching to a compact status indicator. Every pane - keeps running and every alert still fires whether you can see it or - not. + You're used to doing{" "} + Shift+Enter{" "} + to get a newline in the browser, but it's broken in your terminal? + Not anymore. Dormouse works the way a user would expect, no arcane + terminal knowledge required!

- Do it all with the mouse, or keep your hands on the keyboard with - tmux keybinds. + Click and drag in a "mouse conformant" terminal doesn't select text; + it sends escape code{" "} + {"\\e[<0;x;yM"}. + And Ctrl+C{" "} + doesn't copy; it asks your program to kill itself.

+
From 3a98651669ad1546676f34a1d75c9d26cb5cae43 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Mon, 18 May 2026 13:59:16 -0700 Subject: [PATCH 21/24] Fix the /pocket playground. --- website/vite.config.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/website/vite.config.ts b/website/vite.config.ts index a815f43b..a0a3ecc8 100644 --- a/website/vite.config.ts +++ b/website/vite.config.ts @@ -21,4 +21,12 @@ export default defineConfig({ server: { host: true, }, + ssr: { + // Bundle the xterm.js packages during SSR. Their package.json has + // `main` (CJS) but no `exports` field, so Vite's SSR module runner + // picks the CJS entry by default and `import { Terminal } from + // "@xterm/xterm"` fails as a named-export error. Telling Vite to + // bundle them forces it to use the `module` (ESM) entry instead. + noExternal: ["@xterm/xterm", "@xterm/addon-fit"], + }, }); From 47ce12a377bdaa2da6dcb6746959cee4c843a32a Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Mon, 18 May 2026 14:42:18 -0700 Subject: [PATCH 22/24] Update marketing copy. --- website/src/pages/Home.tsx | 27 +++++++++++++-------------- website/src/pages/Pocket.tsx | 8 ++++---- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/website/src/pages/Home.tsx b/website/src/pages/Home.tsx index b6515570..2cf8b3e3 100644 --- a/website/src/pages/Home.tsx +++ b/website/src/pages/Home.tsx @@ -783,8 +783,8 @@ function Home() {

Soft as a mouse, sharp as a tmux

Upgrade your VS Code or native terminal with a flexible multipane - layout. Minimize the tasks you're not watching to a compact status - indicator. + layout. Sleep the tasks you're not watching down to a compact + status indicator.

Do it all with the mouse, or keep your hands on the keyboard with @@ -810,7 +810,7 @@ function Home() { BEL{" "} and{" "} OSC 9/99/777{" "} - for seamless integration with TUI-forward applications. + for native TUI integration.

@@ -820,18 +820,17 @@ function Home() {

Newlines and copy paste like you meant

- You're used to doing{" "} + You're used to{" "} Shift+Enter{" "} - to get a newline in the browser, but it's broken in your terminal? - Not anymore. Dormouse works the way a user would expect, no arcane - terminal knowledge required! + for a newline in the browser — but it's broken in your terminal? + Not anymore. Dormouse works the way you'd expect, no arcane + terminal knowledge required.

Click and drag in a "mouse conformant" terminal doesn't select text; it sends escape code{" "} {"\\e[<0;x;yM"}. - And Ctrl+C{" "} - doesn't copy; it asks your program to kill itself. + Dormouse lets you copy-paste like a human, not a terminal.

@@ -873,7 +872,7 @@ function Home() {
}>Standalone App -

Don't settle for your operating system's built-in terminal, get a nice one!

+

Don't settle for your operating system's built-in terminal. Get a nice one.

Coming next: Dormouse Pocket. - Tether a terminal session to your phone over WebRTC and take a stroll — the Dormouse - alert system buzzes you if there's anything to do. A hosted auto-pairing service comes - later, so you can just leave and keep working, no "I'm walking away" dance. + Tether a terminal session to your phone over WebRTC and take a stroll — Dormouse + buzzes your phone when something needs attention. A hosted auto-pairing service comes + later, so you can close the laptop and walk away, no setup dance.

- Open source and free to self-host, or pay us a little bit and you can use ours. We'll discount for early adopters, so don't miss out! + Open source and free to self-host, or pay a small monthly fee for our hosted version. Early adopters get a launch discount.

diff --git a/website/src/pages/Pocket.tsx b/website/src/pages/Pocket.tsx index edfb3178..c4dd9b02 100644 --- a/website/src/pages/Pocket.tsx +++ b/website/src/pages/Pocket.tsx @@ -267,12 +267,12 @@ function DesktopPocketPage() { to try it out! (WIP)

- Tether a terminal session to your phone over WebRTC and take a stroll — the Dormouse - alert system buzzes you if there's anything to do. A hosted auto-pairing service comes - later, so you can just leave and keep working, no "I'm walking away" dance. + Tether a terminal session to your phone over WebRTC and take a stroll — Dormouse + buzzes your phone when something needs attention. A hosted auto-pairing service comes + later, so you can close the laptop and walk away, no setup dance.

- Open source and free to self-host, or pay us a little bit and you can use ours. We'll discount for early adopters, so don't miss out! + Open source and free to self-host, or pay a small monthly fee for our hosted version. Early adopters get a launch discount.

From 3f934f7ab5079931f4ebaf1b09a0d83d20231260 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Mon, 18 May 2026 14:42:37 -0700 Subject: [PATCH 23/24] Home.md no longer needed. --- Home.md | 77 --------------------------------------------------------- 1 file changed, 77 deletions(-) delete mode 100644 Home.md diff --git a/Home.md b/Home.md deleted file mode 100644 index d02a63b8..00000000 --- a/Home.md +++ /dev/null @@ -1,77 +0,0 @@ -# Dormouse - -So many terminals. -Which ones need attention? -A *dormouse* knows when to wake. - -Multitasking terminal for mice (and hotkey wizards too) - -## Soft as a mouse, sharp as a tmux - -Upgrade your VS Code or native terminal with a flexible multipane layout. Minimize the tasks you're not watching to a compact status indicator. - -Do it all with the mouse, or keep your hands on the keyboard with tmux keybinds. - -## Stop watching terminals spin - -Dormouse tracks activity the same way you do — visual motion. When a pane stops changing for two seconds, it marks the task complete and alerts you. - -Works with any CLI tool that prints to a terminal — no plugins, no configuration. Also supports `BEL` and `OSC 9/99/777` for seamless integration with TUI-forward applications. - -## Newlines and copy paste like you meant - -You're used to doing `Shift+Enter` to get a newline in the browser, but it's broken in your terminal? Not anymore. Dormouse works the way a user would expect, no arcane terminal knowledge required! - -Click and drag in a "mouse conformant" terminal doesn't select text; it sends escape code `\e[<0;x;yM`. And `Ctrl+C` doesn't copy; it asks your program to kill itself. - -## Get Dormouse - -A dormouse knows when to wake up. Multitasking terminal for mice. - -[Try it in the Playground](/playground) - -### VS Code Extension - -Also works in Cursor, Windsurf, Antigravity, or any other VS Code fork. - -- [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=diffplug.dormouse) -- [Open VSX Registry](https://open-vsx.org/extension/diffplug/dormouse) - -### Standalone App - -Don't settle for your operating system's built-in terminal, get a nice one! - -- Mac Silicon -- Windows x64 -- Linux x64 -- [Other](https://github.com/diffplug/dormouse/issues/8) - -#### Installing on Mac - -1. Double-click the downloaded .tar.gz to extract Dormouse.app -2. Drag Dormouse.app to Applications - -#### Installing on Windows - -1. Double-click the downloaded Dormouse-windows-x64-setup.exe -2. If SmartScreen appears: More info → Run anyway - -#### Installing on Linux - -1. Make executable: `chmod +x Dormouse-linux-x86_64.AppImage` -2. Run from terminal or double-click to launch - -## Walk away. Keep going. - -Coming next: [Dormouse Pocket](/pocket). Tether a terminal session to your phone over WebRTC and take a stroll — the Dormouse alert system buzzes you if there's anything to do. A hosted auto-pairing service comes later, so you can just leave and keep working, no "I'm walking away" dance. - -Open source and free to self-host, or pay us a little bit and you can use ours. We'll discount for early adopters, so don't miss out! - -**Notify me when Pocket ships.** This signs you up for my personal devlog [nedshed.dev](https://nedshed.dev) on Substack. The next post will be the launch post, you can unsubscribe any time. - ---- - -Built by [nedshed.dev](https://nedshed.dev) (the labs division of [DiffPlug LLC](https://diffplug.com)). - -- [Dependencies](/dependencies) -- [Report an issue](https://github.com/diffplug/dormouse/issues) From c2ff8ede9e3d15cf26abfa639cde8f0d49f206b6 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Mon, 18 May 2026 15:31:39 -0700 Subject: [PATCH 24/24] Tune homepage hook crossfade timing --- website/src/pages/Home.tsx | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/website/src/pages/Home.tsx b/website/src/pages/Home.tsx index 2cf8b3e3..acd9d99e 100644 --- a/website/src/pages/Home.tsx +++ b/website/src/pages/Home.tsx @@ -34,12 +34,13 @@ const RUNWAY_VH = 300 * HERO_SLOMO_FACTOR; /** Scroll thresholds within the pinned runway (0–1) */ const ICON_INITIAL_HIDE_FRAC = 0.67; // Fraction of icon's rendered height hidden at load — leaves top third visible -const HOOK_FADE_REMAINING = 0.10; // Hook begins fading when bottom 10% of icon enters viewport +const HOOK_CROSSFADE_START = 0.05; +const HOOK_CROSSFADE_DURATION = 0.08; const WORD_THRESHOLDS = [0.25, 0.40, 0.55] as const; const FOOTNOTE_THRESHOLD = 0.65; const HEADER_REVEAL_LEAD = 0.04; /** Runway fractions over which the dormouse line fades out. The dormouse - * line fades IN crossfaded with lines 1+2 (shared HOOK_FADE_REMAINING), + * line fades IN crossfaded with lines 1+2 (shared hookFadeProgress), * then keeps carrying the brand alone until "Multitasking" pops in at * WORD_THRESHOLDS[0] — this range governs that final exit. */ const DORMOUSE_LINE_FADE_OUT_START = 0.17; @@ -549,14 +550,12 @@ function Home() { video.style.transform = mediaTransform; poster.style.transform = mediaTransform; - // Hook text: visible until the icon nearly finishes rising, then fades out. - // hookFadeProgress: 0 = fully visible, 1 = fully gone. Tied to the - // icon's last `HOOK_FADE_REMAINING` of climb. Shared with the dormouse - // line so its fade-in crossfades exactly with this fade-out. - const remainingHidden = iconHeight > 0 ? iconCurrentOffset / iconHeight : 0; - const hookFadeProgress = iconCurrentOffset === 0 - ? 1 - : clamp01(1 - remainingHidden / HOOK_FADE_REMAINING); + // Hook text: visible on load, then fades out early in the runway. + // hookFadeProgress: 0 = fully visible, 1 = fully gone. Shared with the + // dormouse line so its fade-in crossfades exactly with this fade-out. + const hookFadeProgress = clamp01( + (fraction - HOOK_CROSSFADE_START) / HOOK_CROSSFADE_DURATION + ); if (hookRef.current) { hookRef.current.style.opacity = String(snapProgress(1 - hookFadeProgress)); hookRef.current.style.transform = `translateY(${-hookFadeProgress * 24}px)`;