-
pnpm dev
+
pnpm dev
{'~/projects/dormouse ❯ pnpm dev\n\n VITE ready\n ➜ Local: http://localhost:5173/'}
;
}
-/** Open the terminal context from the bell of a Wall whose one pane is `pane`. */
+/** Open the terminal context from the header of a Wall whose one pane is `pane`. */
function contextDialogStory(pane: PanePriming): Story {
return {
render: ContextWallStory,
@@ -210,20 +210,20 @@ function contextDialogStory(pane: PanePriming): Story {
// Output for the pane's terminal, which `settleTerminals` waits on.
fakePty: { scenario: flattenScenario(SCENARIO_SHELL_PROMPT) },
},
- play: openAlertRightClickDialog,
+ play: openHeaderRightClickDialog,
};
}
/** Wait for priming before opening the source's alert controls in context. */
-async function openAlertRightClickDialog() {
+async function openHeaderRightClickDialog() {
await waitForPrimedState();
- const alertButton = await requireElement
(
- `[data-alert-button-for="${SESSION_ID}"]`,
- 'alert bell',
+ const header = await requireElement(
+ `[data-pane-header-for="${SESSION_ID}"]`,
+ 'pane header',
);
- const rect = alertButton.getBoundingClientRect();
- alertButton.dispatchEvent(new MouseEvent('contextmenu', {
+ const rect = header.getBoundingClientRect();
+ header.dispatchEvent(new MouseEvent('contextmenu', {
bubbles: true,
cancelable: true,
button: 2,
@@ -234,38 +234,6 @@ async function openAlertRightClickDialog() {
await settleTerminals();
}
-/**
- * Hover the bell so its tooltip renders — the tooltip is what names the action,
- * e.g. `[a] Dismiss alert` vs `[a] Alert settings`.
- *
- * Hover rather than focus: a programmatic `.focus()` does not reliably drive
- * React's `onFocus` here, while `mouseover` is exactly what React synthesizes
- * `onMouseEnter` from. Retried because the primed-state decorator applies over
- * two rAFs and can re-render the header out from under an early hover; throws
- * if the tooltip never appears, so a regression surfaces in the Interactions
- * panel instead of as a silently empty snapshot.
- */
-async function hoverAlertButton() {
- await waitForPrimedState();
- const start = performance.now();
- while (performance.now() - start < RETRY_BUDGET_MS) {
- const bell = document.querySelector(`[data-alert-button-for="${SESSION_ID}"]`);
- const rect = bell?.getBoundingClientRect();
- if (bell && rect) {
- bell.dispatchEvent(new MouseEvent('mouseover', {
- bubbles: true,
- cancelable: true,
- relatedTarget: document.body,
- clientX: rect.left + rect.width / 2,
- clientY: rect.top + rect.height / 2,
- }));
- }
- await wait(50);
- if (document.querySelector('[role="tooltip"]')) return;
- }
- throw new Error('alert bell tooltip never rendered');
-}
-
/**
* Open the TODO pill's notification preview.
*
@@ -274,8 +242,8 @@ async function hoverAlertButton() {
* is exactly what React synthesizes `onMouseEnter` from — neither adds a visual
* state of its own (the pill's hover tint is CSS `:hover`, which a synthetic
* event never sets). Retried, and throws if the preview never appears, for the
- * same reason as `hoverAlertButton`: silently snapshotting a header with no
- * preview is the failure this story exists to catch.
+ * same reason as `openHeaderRightClickDialog`: silently snapshotting a header
+ * with no preview is the failure this story exists to catch.
*/
async function openTodoNotificationPreview() {
await waitForPrimedState();
@@ -415,34 +383,14 @@ const meta: Meta = {
export default meta;
type Story = StoryObj;
-export const AlertDisabled: Story = {
- parameters: primedPane({ status: 'WATCHING_DISABLED' }),
-};
-
-export const AlertEnabled: Story = {
+export const Default: Story = {
parameters: primedPane({ status: 'NOTHING_TO_SHOW' }),
};
-export const AlertMightBeBusy: Story = {
- parameters: primedPane({ status: 'MIGHT_BE_BUSY' }),
-};
-
-export const AlertBusy: Story = {
- parameters: primedPane({ status: 'BUSY' }),
-};
-
-export const AlertMightNeedAttention: Story = {
- parameters: primedPane({ status: 'MIGHT_NEED_ATTENTION' }),
-};
-
-export const AlertRinging: Story = {
- parameters: primedPane({ status: 'ALERT_RINGING' }),
-};
-
// --- Command-keyed WATCHING (docs/specs/alert.md) --------------------------
//
-// The bell acts on the *running command's* rule, not on this pane, so what it
-// offers depends on what the pane is running and whether a rule already exists.
+// The context acts on the *running command's* rule, not on this pane, so what
+// it offers depends on what the pane is running and whether a rule exists.
export const AlertRightClickDialog: Story = contextDialogStory({
status: 'NOTHING_TO_SHOW',
@@ -455,21 +403,6 @@ export const AlertDialogNoCommandRunning: Story = contextDialogStory({
command: null,
});
-export const BellTooltipOffersRule: Story = {
- parameters: primedPane({ status: 'WATCHING_DISABLED', command: 'claude --resume' }),
- play: hoverAlertButton,
-};
-
-export const BellTooltipRemovesRule: Story = {
- parameters: primedPane({ status: 'NOTHING_TO_SHOW', command: 'claude --resume' }),
- play: hoverAlertButton,
-};
-
-export const BellTooltipNoCommandRunning: Story = {
- parameters: primedPane({ status: 'WATCHING_DISABLED', command: null }),
- play: hoverAlertButton,
-};
-
export const TodoOnly: Story = {
parameters: primedPane({ status: 'WATCHING_DISABLED', todo: true }),
};
@@ -518,29 +451,21 @@ export const NotificationDialogLongBody: Story = contextDialogStory({
command: 'pnpm test',
});
-export const TodoAndAlertEnabled: Story = {
- parameters: primedPane({ status: 'NOTHING_TO_SHOW', todo: true }),
-};
-
-export const TodoAndAlertRinging: Story = {
- parameters: primedPane({ status: 'ALERT_RINGING', todo: true }),
-};
-
-export const CompactWidthWithAlert: Story = {
+export const CompactWidth: Story = {
args: {
width: 220,
},
parameters: primedPane({ status: 'NOTHING_TO_SHOW' }),
};
-export const MinimalWidthWithAlert: Story = {
+export const MinimalWidth: Story = {
args: {
width: 150,
},
parameters: primedPane({ status: 'NOTHING_TO_SHOW' }),
};
-export const LongTitleWithAlertAndTodo: Story = {
+export const LongTitleWithTodoAndRinging: Story = {
args: {
width: 360,
},
@@ -617,7 +542,7 @@ export const NarrowWithMouseCaptureControlsVisible: Story = {
};
// Notepad icons with notes across the full, compact, and minimal tiers.
-// AlertEnabled and MinimalWidthWithAlert cover the empty notepad.
+// Default and MinimalWidth cover the empty notepad.
export const NotepadWithNotes: Story = {
args: { noteCount: 3 },
parameters: primedPane({ status: 'NOTHING_TO_SHOW' }),
diff --git a/lib/src/stories/Wall.stories.tsx b/lib/src/stories/Wall.stories.tsx
index 561327da1..5bb324bb2 100644
--- a/lib/src/stories/Wall.stories.tsx
+++ b/lib/src/stories/Wall.stories.tsx
@@ -99,8 +99,8 @@ async function minimizeFirstVisiblePane() {
}
async function openAlertDialog() {
- const alertButton = await requireElement('[data-alert-button-for]', 'alert bell');
- alertButton.dispatchEvent(new MouseEvent('contextmenu', { bubbles: true, cancelable: true, button: 2 }));
+ const header = await requireElement('[data-pane-header-for]', 'pane header');
+ header.dispatchEvent(new MouseEvent('contextmenu', { bubbles: true, cancelable: true, button: 2 }));
await requireElement('[data-terminal-context]', 'terminal context');
await settleTerminals();
}
@@ -161,9 +161,8 @@ export const AlertModalOpen: Story = {
}),
},
play: async () => {
- // Settle first: the bell only offers the context once the primed ALERT_RINGING
- // status has landed, so clicking it earlier is a no-op and the story
- // snapshots a wall with no context.
+ // Settle first: the context reports the primed ALERT_RINGING status, so
+ // opening it earlier would snapshot a wall whose alert rows are still cold.
await settleTerminals();
await openAlertDialog();
},
diff --git a/lib/src/theme-colors.css b/lib/src/theme-colors.css
index b6bbd281f..978369d5b 100644
--- a/lib/src/theme-colors.css
+++ b/lib/src/theme-colors.css
@@ -53,7 +53,7 @@
--color-success: var(--vscode-terminal-ansiGreen);
/* Alarm — per-surface black/white contrast pick, computed at runtime by
- * dynamic-palette.ts from the OKLab lightness of the bg the bell sits on.
+ * dynamic-palette.ts from the OKLab lightness of the bg the alarm sits on.
* The binding below is only the baseline before the dynamic pass runs. */
--color-alarm-vs-header-active: var(--vscode-terminal-ansiYellow);
--color-alarm-vs-header-inactive: var(--vscode-terminal-ansiYellow);
diff --git a/lib/src/theme.css b/lib/src/theme.css
index 24d095a1f..84354e929 100644
--- a/lib/src/theme.css
+++ b/lib/src/theme.css
@@ -39,7 +39,6 @@
--text-sm--line-height: 1rem;
/* Animation */
- --animate-bell-ring: bell-ring 800ms ease-in-out 4;
--animate-alarm-pulse: alarm-pulse 650ms ease-in-out infinite;
/* Four cycles, so an unattended ring leaves no animation running. */
--animate-alarm-pulse-burst: alarm-pulse 650ms ease-in-out 4;
@@ -60,11 +59,6 @@ body {
--font-mono: var(--vscode-editor-font-family);
}
-@keyframes bell-ring {
- 0%, 100% { rotate: 45deg; }
- 50% { rotate: -45deg; }
-}
-
@keyframes alarm-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.55; }
diff --git a/scripts/pairing-walkthrough/README.md b/scripts/pairing-walkthrough/README.md
index 773e73714..ab1f0ffa3 100644
--- a/scripts/pairing-walkthrough/README.md
+++ b/scripts/pairing-walkthrough/README.md
@@ -79,7 +79,7 @@ it.
| 5 | `qr` | Clicks **Set up a phone**, waits for the code, screenshots, crops to the QR, makes a camera-shaped Y4M, and decodes the crop to prove it is legible. → `qr-full.png`, `qr.png`, `qr.y4m`, `invitation-url.txt` |
| 6 | `pocket` | Launches a second, isolated Chrome with the fake camera pointed at `qr.y4m`, attaches with `agent-browser connect `, opens the **plain origin**, and gives the page a CDP virtual authenticator. → `05-pocket-first-run.png` |
| 7 | `code` | Taps **Scan a setup code**; Pocket's own scanner decodes the fake camera, registers a passkey with the scanned token, signs in, and shows two digits. Reads them, and waits for the Burrow's modal to open. → `06-scanner.png`, `07-code-screen.png`, `08-burrow-pairing-modal.png`, `pairing-code.txt` |
-| 8 | `terminal` | Types the two digits into the Burrow's modal and authorizes; waits for Pocket to connect itself and land on the terminal; runs a command from the phone and reads the file it wrote; rings the Burrow and finds the bell on the phone; then leaves to the Burrows view and connects again. → `09-burrow-approved.png` … `14-pocket-reconnected.png`, `terminal-proof.txt`, `notify-proof.txt`, `reconnect-proof.txt` |
+| 8 | `terminal` | Types the two digits into the Burrow's modal and authorizes; waits for Pocket to connect itself and land on the terminal; runs a command from the phone and reads the file it wrote; rings the Burrow and finds the alarm on the phone; then leaves to the Burrows view and connects again. → `09-burrow-approved.png` … `14-pocket-reconnected.png`, `terminal-proof.txt`, `notify-proof.txt`, `reconnect-proof.txt` |
| 8′ | `mismatch` | (`wrong-code`) Types the *next* two digits instead, and waits for the panel to report a mismatch; checks the paired count did not move and follows the phone back to its list. → `09-burrow-mismatch.png`, `10-pocket-mismatch.png` |
| 8′ | `cancel` | (`denied`) Presses the modal's Cancel and waits for the panel to report it; same two checks. → `09-burrow-cancelled.png`, `10-pocket-cancelled.png` |
| 7′ | `dead-code` | (`expired-code`) Replaces the camera's Y4M with a blank frame, opens the scanner, and pastes the Burrow's own code re-issued twice — once stamped with a 2023 expiry, once for another origin as well. Waits for the phone's own sentence each time, and checks the two differ. → `06-pocket-expired.png`, `07-pocket-foreign.png` |
@@ -158,7 +158,7 @@ not healthier than one that has them.
`summary.json` also carries what only a run can know: the decoded pairing URL
and how much of its TTL was left, the round trip from Enter to the file the
-laptop's shell wrote (`terminal.roundTripMs`, ~220 ms here), the Enter-to-bell
+laptop's shell wrote (`terminal.roundTripMs`, ~220 ms here), the Enter-to-alarm
time, and the authenticator's `signCount` after each ceremony. `options` holds
what the run chose for itself. The setup password is not among them — the
Relay mints its own, and a `--keep` run is signed into by hand with the
diff --git a/scripts/pairing-walkthrough/steps.mjs b/scripts/pairing-walkthrough/steps.mjs
index 38018191f..93b04eca6 100644
--- a/scripts/pairing-walkthrough/steps.mjs
+++ b/scripts/pairing-walkthrough/steps.mjs
@@ -1109,7 +1109,7 @@ async function ringFromBurrow(ctx) {
notification: {
sequence: NOTIFY_SEQUENCE,
deliveredInMs: sent.roundTripMs,
- // Enter to a bell on the phone, the tap that opens the session list
+ // Enter to an alarm on the phone, the tap that opens the session list
// included — the ring is normally there before the list is looked at.
visibleInMs: Date.now() - startedAt,
row,
@@ -1262,7 +1262,7 @@ function wallReadyExpr() {
* The session list as the reserve renders it, found by position rather than by
* class: it is the block directly under the input-mode selector, and each row is
* one button carrying the pane's title, its TODO pill, and — when the Burrow says
- * the pane is ringing — a second icon, the bell
+ * the pane is ringing — an alarm inset, an overlay span inside the button
* (`lib/src/components/MobileTerminalUi.tsx`).
*
* A statement, not an expression: it leaves the rows in `rows` (falsy while the
@@ -1274,7 +1274,7 @@ function sessionRowsExpr() {
const rows = reserve && [...reserve.querySelectorAll('button')].map((row) => ({
text: row.innerText.trim(),
todo: [...row.querySelectorAll('span')].some((el) => el.textContent.trim() === 'TODO'),
- ringing: row.querySelectorAll('svg').length > 1,
+ ringing: row.querySelector('[data-alert-ring-inset]') !== null,
}));`;
}
diff --git a/scripts/spec-word-budgets.json b/scripts/spec-word-budgets.json
index 5708c5379..0bb2d2eca 100644
--- a/scripts/spec-word-budgets.json
+++ b/scripts/spec-word-budgets.json
@@ -2,7 +2,7 @@
"AGENTS.md": 3400,
"SECURITY.md": 200,
"SELF_HOST.md": 6200,
- "docs/specs/alert.md": 7250,
+ "docs/specs/alert.md": 7100,
"docs/specs/auto-update.md": 1200,
"docs/specs/deploy.md": 1900,
"docs/specs/dor-browser.md": 4700,
@@ -11,7 +11,7 @@
"docs/specs/glossary.md": 3000,
"docs/specs/hosted.md": 1050,
"docs/specs/layout.md": 10000,
- "docs/specs/mobile-terminal-ui.md": 1950,
+ "docs/specs/mobile-terminal-ui.md": 2000,
"docs/specs/mouse-and-clipboard.md": 3800,
"docs/specs/notepad.md": 4000,
"docs/specs/pocket-app.md": 4900,
diff --git a/vscode-ext/README.md b/vscode-ext/README.md
index f4903bfda..14560d659 100644
--- a/vscode-ext/README.md
+++ b/vscode-ext/README.md
@@ -38,10 +38,7 @@ Dormouse can owe you attention in three independent ways. Two of them need no se
Dormouse never guesses which commands deserve an alert. Watching is a rule you create on a command name, and turning it off anywhere removes it everywhere.
--
no watch rule for this pane's command
--
this command is watched
--
a watched command is running; it will alert when it goes quiet
--
finished, and it needs your attention
+A ringing pane is outlined and washed in the alarm color until you attend it, so one glance across a full wall finds it. Attending the pane — or pressing `a` — puts the alarm out.
Whichever way a pane rings, the ring becomes a **TODO** — a marker beside the pane's title that outlives the alert, so a ring you dismissed does not disappear without a trace. Clear it by clicking it or pressing `t` in command mode.
diff --git a/vscode-ext/images/alert-armed.gif b/vscode-ext/images/alert-armed.gif
deleted file mode 100644
index 60cfe9b7c..000000000
Binary files a/vscode-ext/images/alert-armed.gif and /dev/null differ
diff --git a/vscode-ext/images/alert-disabled.gif b/vscode-ext/images/alert-disabled.gif
deleted file mode 100644
index 21d5a25e7..000000000
Binary files a/vscode-ext/images/alert-disabled.gif and /dev/null differ
diff --git a/vscode-ext/images/alert-enabled.gif b/vscode-ext/images/alert-enabled.gif
deleted file mode 100644
index 484d1c8cf..000000000
Binary files a/vscode-ext/images/alert-enabled.gif and /dev/null differ
diff --git a/vscode-ext/images/alert-ringing.gif b/vscode-ext/images/alert-ringing.gif
deleted file mode 100644
index aec94e405..000000000
Binary files a/vscode-ext/images/alert-ringing.gif and /dev/null differ
diff --git a/vscode-ext/src/workspace-chrome.ts b/vscode-ext/src/workspace-chrome.ts
index f11318929..d144e520b 100644
--- a/vscode-ext/src/workspace-chrome.ts
+++ b/vscode-ext/src/workspace-chrome.ts
@@ -3,16 +3,8 @@ import type { WorkspaceUnion } from '../../lib/src/lib/workspace-union';
const BASE_TITLE = 'Dormouse';
-/**
- * Reflect a Workspace's union status onto a webview's native chrome title,
- * matching the in-app ` [TODO]` pattern: append ` 🔔` when any
- * terminal Session is ringing and ` [TODO]` when any surface is flagged. Both
- * can appear, bell first; clear → just the base title.
- *
- * A tab title is plain text, so the bell is the emoji stand-in for the in-app
- * bell icon and TODO is the bracketed word (not an emoji). See
- * `docs/specs/vscode.md`.
- */
+/** A Workspace's union status as a native chrome title suffix, the plain-text
+ * stand-in for what the Wall draws (`docs/specs/vscode.md`). */
export function workspaceTitle(union: WorkspaceUnion): string {
let title = BASE_TITLE;
if (union.ringing) title += ' 🔔';
diff --git a/website/src/lib/__snapshots__/tut-runner.test.ts.snap b/website/src/lib/__snapshots__/tut-runner.test.ts.snap
index 6d49988bb..e41e67974 100644
--- a/website/src/lib/__snapshots__/tut-runner.test.ts.snap
+++ b/website/src/lib/__snapshots__/tut-runner.test.ts.snap
@@ -60,7 +60,7 @@ exports[`TutRunner snapshots > renders Keyboard navigation with all items incomp
exports[`TutRunner snapshots > renders the alert section with all items incomplete 1`] = `
"[H[2J
- [1mAlerts and attention[0m [2m0/9 complete[0m
+ [1mAlerts and attention[0m [2m0/8 complete[0m
[2m[36mEsc[39m to go back[0m
[33m●[0m [1mAlert me whenever [36mlongtask[39m runs[0m
@@ -68,20 +68,19 @@ exports[`TutRunner snapshots > renders the alert section with all items incomple
[3m(or select it and press [36ma[39m) and turn on "Watch all longtask commands".[0m
[3mAlerts belong to the command, not the tab.[0m
[2m·[0m The rule covers every pane running that command
- [2m·[0m The bell tilts while the command works
[2m·[0m It rings when the command goes quiet
[2m·[0m Dismissing a ringing alert leaves a TODO behind
[2m·[0m Press [36mEnter[39m inside the pane to clear the TODO
[2m·[0m Add a TODO by hand
- [2m·[0m A program can ring the bell itself
+ [2m·[0m A program can ring on its own
[2m·[0m A long command that finished while you were away
- [2mThree different things can ring the bell: a rule you set on a command name, a[0m
- [2mnotification the program sends, and a long command finishing while you were[0m
+ [2mThree different things can make a pane ring: a rule you set on a command name,[0m
+ [2ma notification the program sends, and a long command finishing while you were[0m
[2melsewhere. None of them ring while you are actually looking at the pane.[0m
[2mPress [36ms[39m to start a fake [36mlongtask[39m.[0m
- [2mPress [36mn[39m for a program that rings the bell itself.[0m
+ [2mPress [36mn[39m for a program that rings on its own.[0m
[2mPress [36mx[39m to start a fake [36mslowbuild[39m.[0m
"
`;
@@ -93,10 +92,10 @@ exports[`TutRunner snapshots > renders the top-level menu 1`] = `
[36m❯[0m [1mMake it yours[0m [2m[0/1 complete][0m
Keyboard navigation [2m[0/7 complete][0m
- Alerts and attention [2m[0/9 complete][0m
+ Alerts and attention [2m[0/8 complete][0m
Copy paste [2m[0/4 complete][0m
Starred on GitHub [2m[not yet][0m
- 🐭 FlappyTerm 🐭 [2m[LOCKED 0/21][0m
+ 🐭 FlappyTerm 🐭 [2m[LOCKED 0/20][0m
[2mReset progress[0m
diff --git a/website/src/lib/tut-detector.test.ts b/website/src/lib/tut-detector.test.ts
index 13a99b09d..de80ce532 100644
--- a/website/src/lib/tut-detector.test.ts
+++ b/website/src/lib/tut-detector.test.ts
@@ -155,28 +155,23 @@ describe("TutDetector", () => {
expect(state.isComplete("kb-arrows")).toBe(true);
});
- it("does not credit al-busy or al-ring when a pane is already in that status at first observation", () => {
+ it("does not credit al-ring when a pane is already ringing at first observation", () => {
const { state, setActivitySnapshot } = makeDetectorHarness();
setActivitySnapshot(new Map([
- ["pane-a", activity("BUSY")],
["pane-b", activity("ALERT_RINGING")],
]));
- expect(state.isComplete("al-busy")).toBe(false);
expect(state.isComplete("al-ring")).toBe(false);
});
- it("credits al-busy and al-ring on a true status transition", () => {
+ it("credits al-ring on a true status transition", () => {
const { state, setActivitySnapshot } = makeDetectorHarness();
setActivitySnapshot(new Map([
["pane-a", activity("NOTHING_TO_SHOW")],
]));
- setActivitySnapshot(new Map([
- ["pane-a", activity("BUSY")],
- ]));
- expect(state.isComplete("al-busy")).toBe(true);
+ expect(state.isComplete("al-ring")).toBe(false);
setActivitySnapshot(new Map([
["pane-a", activity("ALERT_RINGING")],
diff --git a/website/src/lib/tut-detector.ts b/website/src/lib/tut-detector.ts
index d2c9a3a49..9ac902595 100644
--- a/website/src/lib/tut-detector.ts
+++ b/website/src/lib/tut-detector.ts
@@ -212,17 +212,11 @@ export class TutDetector {
this.queueSpreadCheck(id);
}
- // Gate al-busy / al-ring on a true status transition. Without the
- // prev.status check, a pane already in BUSY or ALERT_RINGING at the
- // moment its first activity event fires (e.g. restored state, or a
- // pane spawned after start() that arrives mid-task) would credit
- // the user for work they did not do this session.
- if (
- prev.status !== current.status &&
- (current.status === "BUSY" || current.status === "MIGHT_BE_BUSY")
- ) {
- this.state.markComplete("al-busy");
- }
+ // Gate al-ring on a true status transition. Without the prev.status
+ // check, a pane already in ALERT_RINGING at the moment its first
+ // activity event fires (e.g. restored state, or a pane spawned after
+ // start() that arrives mid-task) would credit the user for work they
+ // did not do this session.
if (prev.status !== "ALERT_RINGING" && current.status === "ALERT_RINGING") {
this.state.markComplete("al-ring");
}
diff --git a/website/src/lib/tut-items.ts b/website/src/lib/tut-items.ts
index 5f4d8084a..8e651b135 100644
--- a/website/src/lib/tut-items.ts
+++ b/website/src/lib/tut-items.ts
@@ -21,7 +21,6 @@ const KEYBOARD_ITEM_IDS = [
const ALERT_ITEM_IDS = [
"al-watch-cmd",
"al-spreads",
- "al-busy",
"al-ring",
"al-todo-auto",
"al-todo-clear",
@@ -203,23 +202,18 @@ export const DESKTOP_SECTIONS: readonly Section[] = [
{
id: 'al-spreads',
title: 'The rule covers every pane running that command',
- hint: 'Both fake tasks light up from the one rule you turned on. Any pane you open later that runs `longtask` will watch too, with no extra clicks.',
- },
- {
- id: 'al-busy',
- title: 'The bell tilts while the command works',
- hint: 'Press `s` again if the task already finished.',
+ hint: 'Both fake tasks are covered by the one rule you set. Any pane you open later that runs `longtask` will watch too, with no extra clicks.',
},
{
id: 'al-ring',
title: 'It rings when the command goes quiet',
hint:
- `Don't type! If you type, Dormouse will think you are paying attention to this task and the bell will not ring. The bell waits until you attend another pane or stop interacting for the inactivity timeout in Alarm settings.`,
+ `Don't type! If you type, Dormouse will think you are paying attention to this task and the pane will not ring. It waits until you attend another pane or stop interacting for the inactivity timeout in Alarm settings.`,
},
{
id: 'al-todo-auto',
title: 'Dismissing a ringing alert leaves a TODO behind',
- hint: 'Click the bell or interact with the pane to dismiss. The TODO is there so a ring you waved away does not vanish without a trace.',
+ hint: 'Interact with the pane, or press `a`, to dismiss. The TODO is there so a ring you waved away does not vanish without a trace.',
},
{
id: 'al-todo-clear',
@@ -232,7 +226,7 @@ export const DESKTOP_SECTIONS: readonly Section[] = [
},
{
id: 'al-notif',
- title: 'A program can ring the bell itself',
+ title: 'A program can ring on its own',
hint: 'Press `n` for a fake build that sends a notification. This needs no rule at all — any program that emits `BEL`, `OSC 9`, `OSC 777`, or `OSC 99` rings, and its message shows on the TODO tag.',
},
{
@@ -243,7 +237,7 @@ export const DESKTOP_SECTIONS: readonly Section[] = [
},
],
prose: [
- 'Three different things can ring the bell: a rule you set on a command name, a notification the program sends, and a long command finishing while you were elsewhere. None of them ring while you are actually looking at the pane.',
+ 'Three different things can make a pane ring: a rule you set on a command name, a notification the program sends, and a long command finishing while you were elsewhere. None of them ring while you are actually looking at the pane.',
],
},
COPY_PASTE_SECTION,
diff --git a/website/src/lib/tut-runner.test.ts b/website/src/lib/tut-runner.test.ts
index b13d90eae..e1a077742 100644
--- a/website/src/lib/tut-runner.test.ts
+++ b/website/src/lib/tut-runner.test.ts
@@ -330,7 +330,7 @@ describe("TutRunner snapshots", () => {
expect(lastFrame()).toContain("🐭 FlappyTerm 🐭");
expect(lastFrame()).not.toContain("???");
- expect(lastFrame()).toContain("[LOCKED 0/21]");
+ expect(lastFrame()).toContain("[LOCKED 0/20]");
expect(lastFrame()).toContain("Dormouse Playground Tutorial");
dispose();
});
diff --git a/website/src/lib/tut-runner.ts b/website/src/lib/tut-runner.ts
index 561e7b938..ab777eff9 100644
--- a/website/src/lib/tut-runner.ts
+++ b/website/src/lib/tut-runner.ts
@@ -49,9 +49,9 @@ const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧",
const SPINNER_INTERVAL_MS = 100;
/** Static "your turn" pointer for the active section item — deliberately not
- * animated, so the checklist doesn't compete for attention with the bell the
- * Alerts section is teaching. (Runner frames are written with
- * `skipActivity`, so animation would no longer tilt the bell either way.) */
+ * animated, so the checklist doesn't compete for attention with the alarm the
+ * Alerts section is teaching. (Runner frames are written with `skipActivity`,
+ * so animation would no longer move the detector either way.) */
const ACTIVE_ITEM_GLYPH = "●";
const STAR_PROMPT_TITLE = "Starred on GitHub";
const FLAPPY_TITLE = "🐭 FlappyTerm 🐭";
@@ -909,7 +909,7 @@ export class TutRunner implements InteractiveProgram {
private renderBusyDemoLines(): string[] {
return [
this.renderDemoLine("s", "longtask", "Fake task", this.busyDemoStart, this.busyDemoDurationMs),
- ` ${DIM}Press \`n\` for a program that rings the bell itself.${RESET}`,
+ ` ${DIM}Press \`n\` for a program that rings on its own.${RESET}`,
this.renderDemoLine("x", "slowbuild", "Slow build", this.commandExitDemoStart, this.commandExitDemoDurationMs),
];
}
@@ -1018,7 +1018,7 @@ export class TutRunner implements InteractiveProgram {
private write(data: string): void {
// Runner frames are UI chrome, not task output — skip the activity
- // tick so enabling WATCHING on the runner pane doesn't tilt the bell
+ // tick so enabling WATCHING on the runner pane doesn't look busy
// every time the menu re-renders.
this.adapter.sendOutput(this.terminalId, data, { skipActivity: true });
}
diff --git a/website/src/pages/PlaygroundDesktop.tsx b/website/src/pages/PlaygroundDesktop.tsx
index ca81535bd..991f57a3f 100644
--- a/website/src/pages/PlaygroundDesktop.tsx
+++ b/website/src/pages/PlaygroundDesktop.tsx
@@ -222,7 +222,7 @@ function PlaygroundDesktopExperience() {
"\x1b]777;notify;Build finished;3 packages rebuilt\x07",
);
},
- // An unwatched command, so the command-exit track owns the bell:
+ // An unwatched command, so the command-exit track owns the ring:
// the user attends the pane, leaves, and the exit rings.
onTriggerCommandExitDemo: (durationMs) => {
startFakeCommand(adapter, PANE_SPLASH, "slowbuild");