Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 18 additions & 14 deletions docs/specs/mobile-ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## 1. Overview

This document specifies the `/tether` mobile terminal prototype.
This document specifies the `/playground/pocket` mobile terminal prototype.

The prototype tests one core idea:

Expand All @@ -13,13 +13,17 @@ Stable terminal viewport + mobile session viewport + explicit touch mode + expli
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 Dormouse `ThemePicker`.
The website `/playground/pocket` prototype exposes a small floating theme
switcher above the terminal. It uses the shared Dormouse `ThemePicker`. On
desktop, `/playground/pocket` shows a share-to-phone page instead of the
interactive terminal. The `/pocket` route temporarily redirects to
`/playground/pocket`; this is a launch-state redirect, not the future real
tethering environment.

`/tether` uses the same fake playground terminal stack as `/playground`:
`PlaygroundShellRegistry` attaches a `TutorialShell` to every spawned pane, the
same fake commands dispatch to browser-side runners, and the first pane simply
auto-runs `ascii-splash` as its initial command.
`/playground/pocket` uses the same fake playground terminal stack as
`/playground/desktop`: `PlaygroundShellRegistry` attaches a `TutorialShell` to
every spawned pane, the same fake commands dispatch to browser-side runners, and
the first pane simply auto-runs `ascii-splash` as its initial command.

## 2. Prototype Goals

Expand Down Expand Up @@ -76,10 +80,10 @@ block should use one divider between the Touch and Input rows, with no divider
above Touch and no divider below Input. The mobile session header should not use
the desktop terminal title corner radius; it is a flush mobile bar. The alert
bell sits immediately after the title before secondary title detail. The mobile
header keeps a minimize button, and in the `/tether` prototype that action opens
the Sessions reserve instead of creating a desktop Door. The Touch row and its
selector tray should sit on `terminal-bg` so they read as part of the terminal
surface above. The Input row and reserve area should sit on
header keeps a minimize button, and in the `/playground/pocket` prototype that
action opens the Sessions reserve instead of creating a desktop Door. The Touch
row and its selector tray should sit on `terminal-bg` so they read as part of
the terminal surface above. The Input row and reserve area should sit on
`header-inactive-bg` with `header-inactive-fg`, so the lower input controls are
distinct from the terminal while still following the selected theme.

Expand Down Expand Up @@ -111,8 +115,8 @@ If Mouse mode is active and the active pane stops capturing mouse events, the
selector must fall back to Gestures.

Gesture mode intentionally consumes primary mouse/trackpad clicks in addition to
touch input. This keeps the `/tether` prototype usable in desktop browsers,
narrow desktop viewports, and Storybook without a touchscreen. A primary
touch input. This keeps the `/playground/pocket` prototype usable in desktop
browsers, narrow desktop viewports, and Storybook without a touchscreen. A primary
mouse/trackpad click in pane content must start radial gesture handling, call
`preventDefault()`, stop propagation, and capture that pointer; it is not passed
through to the embedded `Wall`, xterm, or dockview for focus, selection, or pane
Expand Down Expand Up @@ -376,7 +380,7 @@ Minimum useful behavior:
`tut` is running, `Ctrl+C` sends `\x03` to that app; if the app exits, the
terminal returns to the fake shell prompt instead of restarting the app.
* New panes created from the wall get the same fake shell behavior and prompt as
regular `/playground` panes.
regular `/playground/desktop` panes.

Example commands:

Expand Down
12 changes: 7 additions & 5 deletions docs/specs/theme.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,13 @@ terminal colors. It captures the current DOM-visible theme state and shows:
- dynamic door/focus-ring picks from the same `pickDoorPair()` and
`pickFocusRing()` helpers used by Wall's `computeDynamicPalette()`.

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 `dormouse.debugTheme` command and the
Standalone, playground, and the website `/playground/pocket` prototype expose
the debugger as `Debug current theme` in the `ThemePicker` menu.
`/playground/pocket` uses the same picker in the desktop share page header and
as a floating control above the mobile terminal prototype, both with the Kimbie
Dark default theme fallback. `/pocket` redirects before rendering a picker.
VSCode 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.

Expand Down
31 changes: 17 additions & 14 deletions docs/specs/tutorial.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# 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 Dormouse detects the corresponding action.
The website playground has canonical device-specific routes:

- `/playground` is a client-side dispatcher. It picks Pocket for coarse-pointer devices or narrow viewports and Desktop otherwise, then replaces the history entry with `/playground/desktop` or `/playground/pocket`. The exact media query lives in `website/src/lib/playground-routing.ts`.
- `/playground/desktop` hosts the desktop tiling tutorial. When the dispatcher would have picked Pocket (coarse pointer or narrow viewport) it does not mount `Wall`; it shows a message that the screen is too small for the desktop playground and links to `/playground/pocket`.
- `/playground/pocket` hosts the mobile Pocket playground. On desktop it shows the temporary Pocket marketing/share page from the old `/pocket` route, including the phone preview and notify signup form.
- `/pocket` temporarily redirects to `/playground/pocket`. This is a temporary launch-state redirect; the future real tethering surface should stay separate from the playground URL when it exists.

The interactive desktop TUI lives at `/playground/desktop`. 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

Expand All @@ -13,16 +20,12 @@ Three browser-side pieces in `website/src/lib/`, mirroring the pattern in `websi

## Layout

- `SiteHeader` at top with the `Theme:` dropdown control on `/playground` (other routes do not render it). Header is `themeAware` so `--vscode-*` variables drive its background, border, text, and banner colors.
- `SiteHeader` at top with the `Theme:` dropdown control on `/playground/desktop`. Header is `themeAware` so `--vscode-*` variables drive its background, border, text, and banner colors.
- `<main>` is a flex container so Wall's `flex-1 min-h-0` root gets a real height.
- `Wall` runs `FakePtyAdapter` with `initialMode="passthrough"`. The pane layout branches at mount on `window.innerWidth < 768` (Tailwind's `md` breakpoint, locked at mount; not reactive to resize):
- **Desktop (≥ 768px)** — three panes:
- **`tut-main`** (left, ~50%) — auto-launches `TutRunner` via `mainShell.runCommand("tut")`.
- **`tut-boxed`** (right-top, ~25%) — titled "changelog". Auto-launches `ChangelogRunner` via `boxedShell.runCommand("changelog")`. Doubles as the Copy Rewrapped target — its wrapped lines exercise the rewrap path.
- **`tut-splash`** (right-bottom, ~25%) — titled "ascii-splash". Auto-launches `AsciiSplashRunner` via `splashShell.runCommand("ascii-splash")`.
- **Phone (< 768px)** — two stacked panes; the changelog is dropped because the screen is too narrow to host it usefully:
- **`tut-main`** (top, ~50%) — same as desktop.
- **`tut-splash`** (bottom, ~50%) — same as desktop.
- `Wall` runs `FakePtyAdapter` with `initialMode="passthrough"` on `/playground/desktop`. The route uses the desktop three-pane layout only:
- **`tut-main`** (left, ~50%) — auto-launches `TutRunner` via `mainShell.runCommand("tut")`.
- **`tut-boxed`** (right-top, ~25%) — titled "changelog". Auto-launches `ChangelogRunner` via `boxedShell.runCommand("changelog")`. Doubles as the Copy Rewrapped target — its wrapped lines exercise the rewrap path.
- **`tut-splash`** (right-bottom, ~25%) — titled "ascii-splash". Auto-launches `AsciiSplashRunner` via `splashShell.runCommand("ascii-splash")`.
- Side panes are added in `onApiReady` with `position: { referencePanel, direction }` after Wall creates the initial main pane.

Every playground pane gets a `TutorialShell` input handler through `PlaygroundShellRegistry`. Newly split or spawned fake terminals use `SCENARIO_SHELL_PROMPT` by default. The shell dispatches by command name to a `startProgram` factory provided by the page; the factory wires `tut` → `TutRunner` and `ascii-splash` / `splash` → `AsciiSplashRunner`.
Expand All @@ -31,9 +34,9 @@ Every playground pane gets a `TutorialShell` input handler through `PlaygroundSh

The runner shows a top-level menu first. Selecting a section drills into its item list. Each section shows `[N/M complete]` next to its title. The menu helper below `Dormouse Playground Tutorial` shows only navigation shortcuts, not overall completion.

The top-level menu also includes `Starred on GitHub`, which sits directly below `Copy paste` without a blank spacer, and shows `[not yet]` until selected and `[thanks ⭐]` after it has been resolved. Pressing Enter on that row calls `onOpenGithub`, which `/playground` and the mobile tether page wire to `window.open("https://github.com/diffplug/dormouse", "_blank", "noopener,noreferrer")`.
The top-level menu also includes `Starred on GitHub`, which sits directly below `Copy paste` without a blank spacer, and shows `[not yet]` until selected and `[thanks ⭐]` after it has been resolved. Pressing Enter on that row calls `onOpenGithub`, which `/playground/desktop` and the Pocket playground wire to `window.open("https://github.com/diffplug/dormouse", "_blank", "noopener,noreferrer")`.

After `Starred on GitHub`, the top-level menu shows the mystery row. It is `🐭 ??? 🐭` with `[LOCKED N/M]` while any section task is incomplete. `N/M` is computed from section checklist items only; `Starred on GitHub` and the mystery row do not count. When all section tasks are complete, the row becomes `🐭 Flappy Term 🐭` with a `[High score: N]` readout. Pressing Enter on the unlocked row opens Flappy Term, a runner-local mini-game: `Space`/`Up`/`Enter` flaps the bird, scoring persists as the high score, and `Esc` returns to the top-level menu. On the game-over screen, `Enter` restarts and `p` calls `onOpenPocket`, which `/playground` and the mobile tether page wire to `window.open("https://dormouse.sh/pocket", "_blank", "noopener,noreferrer")`.
After `Starred on GitHub`, the top-level menu shows the mystery row. It is `🐭 ??? 🐭` with `[LOCKED N/M]` while any section task is incomplete. `N/M` is computed from section checklist items only; `Starred on GitHub` and the mystery row do not count. When all section tasks are complete, the row becomes `🐭 Flappy Term 🐭` with a `[High score: N]` readout. Pressing Enter on the unlocked row opens Flappy Term, a runner-local mini-game: `Space`/`Up`/`Enter` flaps the bird, scoring persists as the high score, and `Esc` returns to the top-level menu. On the game-over screen, `Enter` restarts and `p` calls `onOpenPocket`, which `/playground/desktop` and the Pocket playground wire to `window.open("/pocket", "_blank", "noopener,noreferrer")`. The game-over prompt reads `Read about Dormouse Pocket [p]`.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
After `Starred on GitHub`, the top-level menu shows the mystery row. It is `🐭 ??? 🐭` with `[LOCKED N/M]` while any section task is incomplete. `N/M` is computed from section checklist items only; `Starred on GitHub` and the mystery row do not count. When all section tasks are complete, the row becomes `🐭 Flappy Term 🐭` with a `[High score: N]` readout. Pressing Enter on the unlocked row opens Flappy Term, a runner-local mini-game: `Space`/`Up`/`Enter` flaps the bird, scoring persists as the high score, and `Esc` returns to the top-level menu. On the game-over screen, `Enter` restarts and `p` calls `onOpenPocket`, which `/playground/desktop` and the Pocket playground wire to `window.open("/pocket", "_blank", "noopener,noreferrer")`. The game-over prompt reads `Read about Dormouse Pocket [p]`.
After `Starred on GitHub`, the top-level menu shows the mystery row. It is `🐭 ??? 🐭` with `[LOCKED N/M]` while any section task is incomplete. `N/M` is computed from section checklist items only; `Starred on GitHub` and the mystery row do not count. When all section tasks are complete, the row becomes `🐭 Flappy Term 🐭` with a `[High score: N]` readout. Pressing Enter on the unlocked row opens Flappy Term, a runner-local mini-game: `Space`/`Up`/`Enter` flaps the bird, scoring persists as the high score, and `Esc` returns to the top-level menu. On the game-over screen, `Enter` restarts and `p` calls `onOpenPocket`, which `/playground/desktop` and the Pocket playground wire to `window.open("/playground/pocket", "_blank", "noopener,noreferrer")`. The game-over prompt reads `Read about Dormouse Pocket [p]`.


Inside a section, items render as one of:

Expand Down Expand Up @@ -117,14 +120,14 @@ Implemented in `dormouse-lib/lib/themes` and `dormouse-lib/components/ThemePicke

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.
The picker appears on `/playground/desktop` and `/playground/pocket`, labeled `Theme:`. On `/playground/desktop` it is inside the theme-aware `SiteHeader`; on `/playground/pocket` mobile it floats over the terminal; on the desktop Pocket playground page it uses the standalone appbar variant. `/pocket` redirects before rendering a picker. 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.

Each theme is defined as a map of `--vscode-*` CSS variable overrides. `applyTheme()` applies the active theme, which:
1. Cascades into `--color-*` variables (via `var(--vscode-*, fallback)` in `theme.css`)
2. Triggers the `MutationObserver` in `lib/src/lib/terminal-theme.ts` to re-read `getTerminalTheme()` for all xterm.js terminals
3. Updates Dockview/Tailwind token colors

The picker restores the persisted active theme on mount. The playground header is `themeAware`, so the same active theme also affects the site header chrome while the picker remains hidden on non-playground routes.
The picker restores the persisted active theme on mount. The desktop playground header is `themeAware`, so the same active theme also affects that route's site header chrome.

## Mouse and Clipboard Feature Coverage

Expand Down
8 changes: 8 additions & 0 deletions website/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ export const routes: RouteRecord[] = [
path: "/playground",
lazy: () => import("./pages/Playground"),
},
{
path: "/playground/desktop",
lazy: () => import("./pages/PlaygroundDesktop"),
},
{
path: "/playground/pocket",
lazy: () => import("./pages/PocketPlayground"),
},
{
path: "/pocket",
lazy: () => import("./pages/Pocket"),
Expand Down
Loading
Loading