mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-10 09:19:42 +02:00
feat(settings): an Animations section in App Settings
Every animation setting now has its own App Settings section, right after Appearance (owner: easier to find). It holds the Entrance Theme (the former Entrance Animations row), Tile Animations, and an Open lab button that closes settings and opens the per-surface lab (?animlab=1). Appearance keeps the skin, identity and tab settings. New animation settings go in this section; test/app-settings-structure.test.ts pins it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -329,7 +329,7 @@ Codeman is a Claude Code session manager with web interface and autonomous Ralph
|
|||||||
|
|
||||||
Frontend JS modules have `@fileoverview` with `@dependency`/`@loadorder` tags. Load order: `constants.js`(1) → `i18n.js`(1.5) → `mobile-handlers.js`(2) → `voice-input.js`(3) → `notification-manager.js`(4) → `keyboard-accessory.js`(5) → `input-cjk.js`(5.5) → `mobile-ime-preview.js`(5.52) → `terminal-keycode229-recovery.js`(5.55) → `sanitize-html.js`(5.6) → `tab-layout-browser.js`(5.9) → `app.js`(6) → `tab-rail-resize.js`(6.5) → `terminal-ui.js`(7) → `terminal-tile.js`(7.4) → `terminal-split.js`(7.5) → `tile-grid.js`(7.6) → `respawn-ui.js`(8) → `ralph-panel.js`(9) → `orchestrator-panel.js`(9.5) → `cron-ui.js`(9.7) → `settings-ui.js`(10) → `panels-ui.js`(11) → `readmymind-ui.js`(11.3) → `ultracode-panel.js`(11.5) → `approvals-ui.js`(11.6) → `reboot-restore-ui.js`(11.65) → `admin-ui.js`(11.7) → `session-ui.js`(12) → `host-wake-ui.js`(12.2) → `webview-tabs.js`(12.5) → `mobile-overview.js`(12.55) → `home-sessions.js`(12.56) → `git-status-ui.js`(12.57) → `entrance-animations.js`(12.6) → `ralph-wizard.js`(13) → `api-client.js`(14) → `subagent-windows.js`(15) → `ultracode-windows.js`(15.5) → `session-lineage.js`(15.6) → `image-input.js`(16) → `spreadsheet-preview.js`(16.5). `i18n.js` translates static + newly inserted application DOM while skipping terminal/response/file/user-name surfaces; `input-cjk.js` handles CJK IME composition via an always-visible textarea below the terminal (`window.cjkActive` blocks xterm's onData). `terminal-keycode229-recovery.js` forwards a committed `input` event that xterm's `_inputEvent` guard drops (Chrome-on-Android soft keyboards send `composed: true` after a keydown), and only when xterm emitted no canonical data for that keystroke. ⚠️ **That decision is settled at the NEXT keydown as well as on its own zero-delay timer** (#441): the drain runs from xterm's custom key handler, which fires BEFORE xterm processes that key, so a soft keyboard that commits the last character and sends Enter in one InputConnection transaction puts the character on the wire ahead of the `\r`. On the timer alone that character is not merely late, it is LOST: xterm emits the `\r` first and bumps the canonical counter past the candidate's snapshot, so the candidate stands down (measured, `hell\r` where the user typed `hello`). The edit-based diff described below is settled first at that keydown, so the drain decides with the evidence the timer had and Enter's textarea clear cannot turn the pending line into DELs. Ordering is pinned by `test/terminal-keycode229-recovery.browser.test.ts`, which the CI gate does NOT run. The same module replaces xterm's `_handleAnyTextareaChanges` (an append-only `newValue.replace(oldValue, '')` diff) with an edit-based one, so an Android autocorrect on space (delete + insert) reaches the PTY once instead of duplicating the line; ⚠️ that diff is settled at the NEXT keydown, before xterm handles that key, because xterm clears the textarea for Enter and a pending diff would then send one DEL per character ahead of the submitted line (except a composition xterm finalizes synchronously at that key, which stays xterm's). ⚠️ Every xterm that takes keyboard input wires its OWN controller from this module: the primary pane (terminal-ui.js `initTerminal()`) and each `TerminalTile` (terminal-tile.js `_createKeyCode229Recovery()`, so every grid tile and the split's Pane B, which a wide Android tablet reaches), each on its own textarea, composition helper and session; in both, `handleKeyEvent` must run ABOVE the custom key handler's keyCode-229 early return, and `notifyCanonicalData` sits in the onData lambda, never in the send path the recovered bytes also take. `mobile-ime-preview.js` (iOS WebKit only) paints the text an IME is composing: an iOS IME commit is routed into the local-echo overlay through the ordinary printable/paste branch and then `_transferMobileImeCommitToLocalEcho`, and without local echo the preview clears only on output parsed AFTER the commit (or its 2 s fallback). ⚠️ It watches keydown in the capture phase on `terminal.element`, never on the textarea, because xterm finalizes the composition and emits the commit in its own capture listener on the textarea.
|
Frontend JS modules have `@fileoverview` with `@dependency`/`@loadorder` tags. Load order: `constants.js`(1) → `i18n.js`(1.5) → `mobile-handlers.js`(2) → `voice-input.js`(3) → `notification-manager.js`(4) → `keyboard-accessory.js`(5) → `input-cjk.js`(5.5) → `mobile-ime-preview.js`(5.52) → `terminal-keycode229-recovery.js`(5.55) → `sanitize-html.js`(5.6) → `tab-layout-browser.js`(5.9) → `app.js`(6) → `tab-rail-resize.js`(6.5) → `terminal-ui.js`(7) → `terminal-tile.js`(7.4) → `terminal-split.js`(7.5) → `tile-grid.js`(7.6) → `respawn-ui.js`(8) → `ralph-panel.js`(9) → `orchestrator-panel.js`(9.5) → `cron-ui.js`(9.7) → `settings-ui.js`(10) → `panels-ui.js`(11) → `readmymind-ui.js`(11.3) → `ultracode-panel.js`(11.5) → `approvals-ui.js`(11.6) → `reboot-restore-ui.js`(11.65) → `admin-ui.js`(11.7) → `session-ui.js`(12) → `host-wake-ui.js`(12.2) → `webview-tabs.js`(12.5) → `mobile-overview.js`(12.55) → `home-sessions.js`(12.56) → `git-status-ui.js`(12.57) → `entrance-animations.js`(12.6) → `ralph-wizard.js`(13) → `api-client.js`(14) → `subagent-windows.js`(15) → `ultracode-windows.js`(15.5) → `session-lineage.js`(15.6) → `image-input.js`(16) → `spreadsheet-preview.js`(16.5). `i18n.js` translates static + newly inserted application DOM while skipping terminal/response/file/user-name surfaces; `input-cjk.js` handles CJK IME composition via an always-visible textarea below the terminal (`window.cjkActive` blocks xterm's onData). `terminal-keycode229-recovery.js` forwards a committed `input` event that xterm's `_inputEvent` guard drops (Chrome-on-Android soft keyboards send `composed: true` after a keydown), and only when xterm emitted no canonical data for that keystroke. ⚠️ **That decision is settled at the NEXT keydown as well as on its own zero-delay timer** (#441): the drain runs from xterm's custom key handler, which fires BEFORE xterm processes that key, so a soft keyboard that commits the last character and sends Enter in one InputConnection transaction puts the character on the wire ahead of the `\r`. On the timer alone that character is not merely late, it is LOST: xterm emits the `\r` first and bumps the canonical counter past the candidate's snapshot, so the candidate stands down (measured, `hell\r` where the user typed `hello`). The edit-based diff described below is settled first at that keydown, so the drain decides with the evidence the timer had and Enter's textarea clear cannot turn the pending line into DELs. Ordering is pinned by `test/terminal-keycode229-recovery.browser.test.ts`, which the CI gate does NOT run. The same module replaces xterm's `_handleAnyTextareaChanges` (an append-only `newValue.replace(oldValue, '')` diff) with an edit-based one, so an Android autocorrect on space (delete + insert) reaches the PTY once instead of duplicating the line; ⚠️ that diff is settled at the NEXT keydown, before xterm handles that key, because xterm clears the textarea for Enter and a pending diff would then send one DEL per character ahead of the submitted line (except a composition xterm finalizes synchronously at that key, which stays xterm's). ⚠️ Every xterm that takes keyboard input wires its OWN controller from this module: the primary pane (terminal-ui.js `initTerminal()`) and each `TerminalTile` (terminal-tile.js `_createKeyCode229Recovery()`, so every grid tile and the split's Pane B, which a wide Android tablet reaches), each on its own textarea, composition helper and session; in both, `handleKeyEvent` must run ABOVE the custom key handler's keyCode-229 early return, and `notifyCanonicalData` sits in the onData lambda, never in the send path the recovered bytes also take. `mobile-ime-preview.js` (iOS WebKit only) paints the text an IME is composing: an iOS IME commit is routed into the local-echo overlay through the ordinary printable/paste branch and then `_transferMobileImeCommitToLocalEcho`, and without local echo the preview clears only on output parsed AFTER the commit (or its 2 s fallback). ⚠️ It watches keydown in the capture phase on `terminal.element`, never on the textarea, because xterm finalizes the composition and emits the commit in its own capture listener on the textarea.
|
||||||
|
|
||||||
**Entrance animations** (`entrance-animations.js`, all OFF by default): opt-in animations for tabs, terminal, windows, connection lines and tile-grid tiles, chosen via `data-tab-anim` / `data-term-anim` / `data-win-anim` / `data-line-anim` / `data-tile-anim` on `<html>`; the default `legacy` theme short-circuits every hook (tiles keep the grid's own `settle`). ⚠️ Tabs and lines are destroyed mid-animation on re-render, so re-apply to the fresh element by id with a negative `animation-delay` (resume, never restart). ⚠️ Terminal-pane styles may animate only transform / opacity / clip-path (anything else resizes the PTY via FitAddon); `blur` is the ONE sanctioned `filter` exception, do not generalise it. ⚠️ Line glow lives in `--line-glow` so blur keyframes interpolate. ⚠️ Tiles are OFF by default (App Settings → Tile Animations, default `settle` = the grid's own fade, no screen beat); a theme only presets it. A tile's FRAME animates transform/opacity only (six at once, no filter) with `tile-enter*`/`tile-leave*` keyframe names (what the mount and the still copy listen for); its SCREEN replays the pane style on `.tile-body.term-enter`; a reload's restore always settles. Persisted per-device in `codeman:*Anim` localStorage keys, never in `SettingsUpdateSchema`; lab at `?animlab=1`. Test: `test/entrance-animations.test.ts`. → [architecture-invariants#entrance-animations](docs/architecture-invariants.md#entrance-animations)
|
**Entrance animations** (`entrance-animations.js`, all OFF by default): opt-in animations for tabs, terminal, windows, connection lines and tile-grid tiles, chosen via `data-tab-anim` / `data-term-anim` / `data-win-anim` / `data-line-anim` / `data-tile-anim` on `<html>`; the default `legacy` theme short-circuits every hook (tiles keep the grid's own `settle`). ⚠️ Tabs and lines are destroyed mid-animation on re-render, so re-apply to the fresh element by id with a negative `animation-delay` (resume, never restart). ⚠️ Terminal-pane styles may animate only transform / opacity / clip-path (anything else resizes the PTY via FitAddon); `blur` is the ONE sanctioned `filter` exception, do not generalise it. ⚠️ Line glow lives in `--line-glow` so blur keyframes interpolate. Every animation setting lives in App Settings → **Animations** (`#settings-animations`); put new ones there. ⚠️ Tiles are OFF by default (Animations → Tile Animations, default `settle` = the grid's own fade, no screen beat); a theme only presets it. A tile's FRAME animates transform/opacity only (six at once, no filter) with `tile-enter*`/`tile-leave*` keyframe names (what the mount and the still copy listen for); its SCREEN replays the pane style on `.tile-body.term-enter`; a reload's restore always settles. Persisted per-device in `codeman:*Anim` localStorage keys, never in `SettingsUpdateSchema`; lab at `?animlab=1`. Test: `test/entrance-animations.test.ts`. → [architecture-invariants#entrance-animations](docs/architecture-invariants.md#entrance-animations)
|
||||||
|
|
||||||
**Mobile tab strip scrolling** (issue #257): under 768px the tab strip scrolls horizontally, so the active tab must be kept reachable. `_updateActiveTabImmediate()` reveals it via `computeTabScrollLeft()` (constants.js, rect math on the strip's own `scrollLeft`, never `scrollIntoView()`, which scrolls the document under the fixed header); `_fullRenderSessionTabs()` must restore `scrollLeft` across rebuilds and re-reveal only when the active tab changed (`_lastRenderedActiveTabId`) or, grouped by state, the ACTIVE tab moved to another state band (`_noteActiveTabBand()`, both render paths, scrolling row only; another tab's move never scrolls). ⚠️ The phone-block `min-width` on `.session-tab.active .tab-name` keeps the tab's centre off the gear/close icons, sized for numberless tabs 10+ (floor 40px); do not shrink it. ⚠️ Never reintroduce hoisting the active session to the front of the strip. Tests: `test/mobile-tab-tap-zones.test.ts`, `test/tab-triage.test.ts`. → [architecture-invariants#mobile-tab-strip-scrolling](docs/architecture-invariants.md#mobile-tab-strip-scrolling)
|
**Mobile tab strip scrolling** (issue #257): under 768px the tab strip scrolls horizontally, so the active tab must be kept reachable. `_updateActiveTabImmediate()` reveals it via `computeTabScrollLeft()` (constants.js, rect math on the strip's own `scrollLeft`, never `scrollIntoView()`, which scrolls the document under the fixed header); `_fullRenderSessionTabs()` must restore `scrollLeft` across rebuilds and re-reveal only when the active tab changed (`_lastRenderedActiveTabId`) or, grouped by state, the ACTIVE tab moved to another state band (`_noteActiveTabBand()`, both render paths, scrolling row only; another tab's move never scrolls). ⚠️ The phone-block `min-width` on `.session-tab.active .tab-name` keeps the tab's centre off the gear/close icons, sized for numberless tabs 10+ (floor 40px); do not shrink it. ⚠️ Never reintroduce hoisting the active session to the front of the strip. Tests: `test/mobile-tab-tap-zones.test.ts`, `test/tab-triage.test.ts`. → [architecture-invariants#mobile-tab-strip-scrolling](docs/architecture-invariants.md#mobile-tab-strip-scrolling)
|
||||||
|
|
||||||
|
|||||||
@@ -877,7 +877,7 @@ Tests: `test/terminal-touch-tap.test.ts`.
|
|||||||
|
|
||||||
### Entrance animations
|
### Entrance animations
|
||||||
|
|
||||||
**Entrance animations** (`entrance-animations.js`, all OFF by default): opt-in animations for the five things that appear when work starts, chosen per surface via `data-tab-anim` / `data-term-anim` / `data-win-anim` / `data-line-anim` / `data-tile-anim` on `<html>`. Defaults are the `legacy` theme, so an untouched install behaves exactly as before and every hook short-circuits on its first line. Persisted to its own `codeman:*Anim` localStorage keys (per-device, deliberately NOT in the `.strict()` `SettingsUpdateSchema`); picker in App Settings → Appearance, full per-surface lab at `?animlab=1`.
|
**Entrance animations** (`entrance-animations.js`, all OFF by default): opt-in animations for the five things that appear when work starts, chosen per surface via `data-tab-anim` / `data-term-anim` / `data-win-anim` / `data-line-anim` / `data-tile-anim` on `<html>`. Defaults are the `legacy` theme, so an untouched install behaves exactly as before and every hook short-circuits on its first line. Persisted to its own `codeman:*Anim` localStorage keys (per-device, deliberately NOT in the `.strict()` `SettingsUpdateSchema`); picker in its own App Settings → **Animations** section (`#settings-animations`: the Entrance Theme, Tile Animations, and an Open lab button), full per-surface lab at `?animlab=1`. New animation settings go in that section.
|
||||||
|
|
||||||
⚠️ Tabs and connection lines are **destroyed mid-animation** on every re-render (`_fullRenderSessionTabs()` replaces the strip's innerHTML; `_updateConnectionLinesImmediate()` does `svg.innerHTML = ''`), so both are tracked by id and re-applied to the fresh element with a **negative `animation-delay`** to resume rather than restart.
|
⚠️ Tabs and connection lines are **destroyed mid-animation** on every re-render (`_fullRenderSessionTabs()` replaces the strip's innerHTML; `_updateConnectionLinesImmediate()` does `svg.innerHTML = ''`), so both are tracked by id and re-applied to the fresh element with a **negative `animation-delay`** to resume rather than restart.
|
||||||
|
|
||||||
@@ -889,7 +889,7 @@ Tests: `test/terminal-touch-tap.test.ts`.
|
|||||||
|
|
||||||
⚠️ Window styles other than `beam` transform the window, which moves the rect its connection line is aimed at; `beam` deliberately animates opacity/filter only so its line can draw toward a stable target.
|
⚠️ Window styles other than `beam` transform the window, which moves the rect its connection line is aimed at; `beam` deliberately animates opacity/filter only so its line can draw toward a stable target.
|
||||||
|
|
||||||
**Tile grid entrances (the fifth surface, `data-tile-anim`).** A tile plays in two beats: its FRAME enters as it mounts in a `TILE_ANIM_STYLES` style (`settle`, `fly`, `deal`, `crt`, `beam`, `cascade`, `pop`, `soft`, `off`), and its SCREEN (`.tile-body`) plays the terminal pane's style when the load queue reports its first capture, through the same `html[data-term-anim]` rules (each names `.terminal-container.term-enter` AND `.tile-body.term-enter`). Content that lands while the frame is still entering waits for it (`--tile-screen-delay`), and the screen's `::before` wash fills `forwards` only, so a held wash never shows as a bright static block. Tiles are OFF by default: the style is its own setting, App Settings → Appearance → **Tile Animations** (`#appSettingsTileAnim`, `codeman:tileAnim`), whose default `settle` is the grid's own fade and settle (`.tile--entering`, pinned by `test/tile-grid-motion.test.ts`) with no screen beat and the plain exit, exactly as before. No saved key means `settle`, so a theme saved before tiles existed gives them nothing new. Picking a theme PRESETS the tile style (every theme carries a `tile` key, `legacy` → `settle`), but the theme readout (`currentAnimTheme`) is matched on the four classic surfaces only, so changing the tile row afterwards never turns the theme into "Custom". The screen beat plays only for a tile style other than `settle`.
|
**Tile grid entrances (the fifth surface, `data-tile-anim`).** A tile plays in two beats: its FRAME enters as it mounts in a `TILE_ANIM_STYLES` style (`settle`, `fly`, `deal`, `crt`, `beam`, `cascade`, `pop`, `soft`, `off`), and its SCREEN (`.tile-body`) plays the terminal pane's style when the load queue reports its first capture, through the same `html[data-term-anim]` rules (each names `.terminal-container.term-enter` AND `.tile-body.term-enter`). Content that lands while the frame is still entering waits for it (`--tile-screen-delay`), and the screen's `::before` wash fills `forwards` only, so a held wash never shows as a bright static block. Tiles are OFF by default: the style is its own setting, App Settings → Animations → **Tile Animations** (`#appSettingsTileAnim`, `codeman:tileAnim`), whose default `settle` is the grid's own fade and settle (`.tile--entering`, pinned by `test/tile-grid-motion.test.ts`) with no screen beat and the plain exit, exactly as before. No saved key means `settle`, so a theme saved before tiles existed gives them nothing new. Picking a theme PRESETS the tile style (every theme carries a `tile` key, `legacy` → `settle`), but the theme readout (`currentAnimTheme`) is matched on the four classic surfaces only, so changing the tile row afterwards never turns the theme into "Custom". The screen beat plays only for a tile style other than `settle`.
|
||||||
|
|
||||||
⚠️ Tile FRAME keyframes animate **transform and opacity only**, colour on a `::before` wash: six tiles animate at once, so no `filter` (a tile's blur is its screen beat, one tile at a time as the queue serves captures). The keyframes are named `tile-enter*` / `tile-leave*` because that is what the mount's `animationend` and the still copy's removal listen for, ignoring any event with a `pseudoElement`. `test/entrance-animations.test.ts` pins both.
|
⚠️ Tile FRAME keyframes animate **transform and opacity only**, colour on a `::before` wash: six tiles animate at once, so no `filter` (a tile's blur is its screen beat, one tile at a time as the queue serves captures). The keyframes are named `tile-enter*` / `tile-leave*` because that is what the mount's `animationend` and the still copy's removal listen for, ignoring any event with a `pseudoElement`. `test/entrance-animations.test.ts` pins both.
|
||||||
|
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ or settled a question the spec left open. The invariants as built are in
|
|||||||
`test/header-icon-hover.test.ts`.
|
`test/header-icon-hover.test.ts`.
|
||||||
- **The grid opens and closes with a short animation, on by default** (owner request:
|
- **The grid opens and closes with a short animation, on by default** (owner request:
|
||||||
"when clicking on the tile button first make this animation nicer"). It is the grid's
|
"when clicking on the tile button first make this animation nicer"). It is the grid's
|
||||||
own `settle` style, the default of App Settings → Appearance → Tile Animations, which
|
own `settle` style, the default of App Settings → Animations → Tile Animations, which
|
||||||
switches on other styles (`fly` out of the tabs, `deal` from the Tiles button, `crt`,
|
switches on other styles (`fly` out of the tabs, `deal` from the Tiles button, `crt`,
|
||||||
`beam`, ...; docs/architecture-invariants.md#entrance-animations).
|
`beam`, ...; docs/architecture-invariants.md#entrance-animations).
|
||||||
Opening, each tile
|
Opening, each tile
|
||||||
|
|||||||
@@ -90,8 +90,6 @@ every session or only the active tab.
|
|||||||
| Setting | Notes |
|
| Setting | Notes |
|
||||||
| ---------------------- | ----------------------------------------------------------------------------------------- |
|
| ---------------------- | ----------------------------------------------------------------------------------------- |
|
||||||
| Skin | Theme palettes, light ones included. Applied before first paint, so no flash of the wrong theme. |
|
| Skin | Theme palettes, light ones included. Applied before first paint, so no flash of the wrong theme. |
|
||||||
| Entrance Animations | Per-surface animation styles for tabs, terminals, windows, and lineage lines. All default to the legacy no-animation behaviour. |
|
|
||||||
| Tile Animations | How tiles arrive when the tile grid opens and leave when it closes: fly out of their tabs, dealt from the Tiles button, CRT, beam down, cascade, pop or soft; each screen then plays the theme's terminal animation. Off by default (the grid's quick fade); picking an Entrance Animations theme presets it. |
|
|
||||||
| Display Name | Your name in the UI. Cosmetic only; it never renames the package, CLI, API, or storage. |
|
| Display Name | Your name in the UI. Cosmetic only; it never renames the package, CLI, API, or storage. |
|
||||||
| Interface Language | English or Simplified Chinese. Per device. |
|
| Interface Language | English or Simplified Chinese. Per device. |
|
||||||
| Session List Layout | Header tab strip (default), a collapsible left sidebar, or the sidebar with detailed rows. See [The Dashboard](The-Dashboard#session-list-layout). |
|
| Session List Layout | Header tab strip (default), a collapsible left sidebar, or the sidebar with detailed rows. See [The Dashboard](The-Dashboard#session-list-layout). |
|
||||||
@@ -105,6 +103,16 @@ every session or only the active tab.
|
|||||||
| Auto-name Sessions | Titles a new tab after its first prompt, keeping the case prefix (`w3-myapp: fix the login redirect`). Synced, off by default. See [The Dashboard](The-Dashboard#automatic-session-names). |
|
| Auto-name Sessions | Titles a new tab after its first prompt, keeping the case prefix (`w3-myapp: fix the login redirect`). Synced, off by default. See [The Dashboard](The-Dashboard#automatic-session-names). |
|
||||||
| Overview Home Screen | The phone home screen. On by default. |
|
| Overview Home Screen | The phone home screen. On by default. |
|
||||||
|
|
||||||
|
### Animations
|
||||||
|
|
||||||
|
All per device, all off by default, applied as you pick them.
|
||||||
|
|
||||||
|
| Setting | Notes |
|
||||||
|
| ---------------------- | ----------------------------------------------------------------------------------------- |
|
||||||
|
| Entrance Theme | One look for how new tabs, terminal panes, agent windows and their lines arrive (Terminal, Beam down, Launch, Soft focus, Quiet, Playful). Off by default. |
|
||||||
|
| Tile Animations | How tiles arrive when the tile grid opens and leave when it closes: fly out of their tabs, dealt from the Tiles button, CRT, beam down, cascade, pop or soft; each screen then plays the theme's terminal animation. Off by default (the grid's quick fade); picking a theme presets it. |
|
||||||
|
| Animation Lab | Opens the per-surface lab (the same as `?animlab=1`): every style side by side, with replay, stagger and speed. Closes settings first. |
|
||||||
|
|
||||||
### Models
|
### Models
|
||||||
|
|
||||||
Claude model cards, the 1M context window switch, the thinking effort segment and the
|
Claude model cards, the 1M context window switch, the thinking effort segment and the
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
*
|
*
|
||||||
* Everything is OFF by default (the `legacy` theme), so an untouched install
|
* Everything is OFF by default (the `legacy` theme), so an untouched install
|
||||||
* behaves exactly as it did before this module existed. Opt in via App Settings
|
* behaves exactly as it did before this module existed. Opt in via App Settings
|
||||||
* → Appearance → Entrance Animations.
|
* → Animations.
|
||||||
*
|
*
|
||||||
* Four constraints shape the design:
|
* Four constraints shape the design:
|
||||||
*
|
*
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
* `_onSessionCreated`.
|
* `_onSessionCreated`.
|
||||||
*
|
*
|
||||||
* Tiles are off by default (`settle`, the grid's own quick fade, exactly as
|
* Tiles are off by default (`settle`, the grid's own quick fade, exactly as
|
||||||
* before) and switched on in App Settings → Appearance → Tile Animations, or
|
* before) and switched on in App Settings → Animations → Tile Animations, or
|
||||||
* preset by a theme. A styled tile plays in two beats that combine two
|
* preset by a theme. A styled tile plays in two beats that combine two
|
||||||
* surfaces. The FRAME enters as it mounts, in its own style
|
* surfaces. The FRAME enters as it mounts, in its own style
|
||||||
* (TILE_ANIM_STYLES); the SCREEN plays the terminal pane's style when its
|
* (TILE_ANIM_STYLES); the SCREEN plays the terminal pane's style when its
|
||||||
@@ -171,7 +171,7 @@ const ANIM_THEMES = [
|
|||||||
/**
|
/**
|
||||||
* The surfaces a theme is recognised by. A theme also PRESETS the tile style
|
* The surfaces a theme is recognised by. A theme also PRESETS the tile style
|
||||||
* when it is picked, but the tile style is its own setting (App Settings →
|
* when it is picked, but the tile style is its own setting (App Settings →
|
||||||
* Appearance → Tile Animations, off by default), so changing it afterwards
|
* Animations → Tile Animations, off by default), so changing it afterwards
|
||||||
* does not turn the theme into "Custom".
|
* does not turn the theme into "Custom".
|
||||||
*/
|
*/
|
||||||
const ANIM_SURFACES = ['tab', 'win', 'line', 'term'];
|
const ANIM_SURFACES = ['tab', 'win', 'line', 'term'];
|
||||||
@@ -180,8 +180,8 @@ const ANIM_SURFACES = ['tab', 'win', 'line', 'term'];
|
|||||||
* Defaults are the `legacy` theme: every entrance OFF, and agent windows on the
|
* Defaults are the `legacy` theme: every entrance OFF, and agent windows on the
|
||||||
* `fly` behaviour Codeman already had before this module existed. So a user who
|
* `fly` behaviour Codeman already had before this module existed. So a user who
|
||||||
* never opens the picker sees exactly the pre-existing UI, and each mark/apply
|
* never opens the picker sees exactly the pre-existing UI, and each mark/apply
|
||||||
* hook short-circuits on its first line. Opt in via App Settings → Appearance →
|
* hook short-circuits on its first line. Opt in via App Settings → Animations,
|
||||||
* Entrance Animations, which persists to the localStorage keys below.
|
* which persists to the localStorage keys below.
|
||||||
*/
|
*/
|
||||||
const TAB_ANIM_DEFAULT = 'off';
|
const TAB_ANIM_DEFAULT = 'off';
|
||||||
const WIN_ANIM_DEFAULT = 'fly';
|
const WIN_ANIM_DEFAULT = 'fly';
|
||||||
@@ -338,7 +338,7 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
return match ? match.key : 'custom';
|
return match ? match.key : 'custom';
|
||||||
},
|
},
|
||||||
|
|
||||||
// ── App Settings picker ───────────────────────────────────────────────────
|
// ── App Settings → Animations ─────────────────────────────────────────────
|
||||||
//
|
//
|
||||||
// Wired straight to setAnimTheme() rather than through saveAppSettings(): the
|
// Wired straight to setAnimTheme() rather than through saveAppSettings(): the
|
||||||
// styles live in their own localStorage keys, so they stay per-device and never
|
// styles live in their own localStorage keys, so they stay per-device and never
|
||||||
@@ -357,6 +357,16 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// App Settings → Animations → Animation Lab. Settings has no unsaved-edit
|
||||||
|
// tracking, so this closes it as Cancel does (the row says so).
|
||||||
|
const labBtn = document.getElementById('appSettingsOpenAnimLab');
|
||||||
|
if (labBtn && !labBtn.dataset.bound) {
|
||||||
|
labBtn.dataset.bound = '1';
|
||||||
|
labBtn.addEventListener('click', () => {
|
||||||
|
this.closeAppSettings?.();
|
||||||
|
this.openAnimLab();
|
||||||
|
});
|
||||||
|
}
|
||||||
// Tile Animations: its own row, off (`settle`) by default. A theme picked
|
// Tile Animations: its own row, off (`settle`) by default. A theme picked
|
||||||
// above presets it; picked here, it applies to tiles alone.
|
// above presets it; picked here, it applies to tiles alone.
|
||||||
const tileSel = document.getElementById('appSettingsTileAnim');
|
const tileSel = document.getElementById('appSettingsTileAnim');
|
||||||
|
|||||||
+69
-34
@@ -1663,6 +1663,10 @@
|
|||||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 3a9 9 0 0 0 0 18 4.5 4.5 0 0 0 0-9 4.5 4.5 0 0 1 0-9z"/></svg>
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 3a9 9 0 0 0 0 18 4.5 4.5 0 0 0 0-9 4.5 4.5 0 0 1 0-9z"/></svg>
|
||||||
<span>Appearance</span>
|
<span>Appearance</span>
|
||||||
</button>
|
</button>
|
||||||
|
<button type="button" class="set-rail-item" data-section="settings-animations">
|
||||||
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3l1.8 4.7L18.5 9.5l-4.7 1.8L12 16l-1.8-4.7L5.5 9.5l4.7-1.8z"/><path d="M19 15l.8 2.2L22 18l-2.2.8L19 21l-.8-2.2L16 18l2.2-.8z"/><path d="M3 19h6"/></svg>
|
||||||
|
<span>Animations</span>
|
||||||
|
</button>
|
||||||
<button type="button" class="set-rail-item" data-section="settings-models">
|
<button type="button" class="set-rail-item" data-section="settings-models">
|
||||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3l8 4.5v9L12 21l-8-4.5v-9L12 3z"/><path d="M12 12l8-4.5M12 12v9M12 12L4 7.5"/></svg>
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3l8 4.5v9L12 21l-8-4.5v-9L12 3z"/><path d="M12 12l8-4.5M12 12v9M12 12L4 7.5"/></svg>
|
||||||
<span>Models</span>
|
<span>Models</span>
|
||||||
@@ -2053,7 +2057,7 @@
|
|||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 3a9 9 0 0 0 0 18 4.5 4.5 0 0 0 0-9 4.5 4.5 0 0 1 0-9z"/></svg>
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 3a9 9 0 0 0 0 18 4.5 4.5 0 0 0 0-9 4.5 4.5 0 0 1 0-9z"/></svg>
|
||||||
<h2>Appearance</h2>
|
<h2>Appearance</h2>
|
||||||
</div>
|
</div>
|
||||||
<p class="set-section-blurb">Theme, motion, and what this install calls itself.</p>
|
<p class="set-section-blurb">Theme, and what this install calls itself.</p>
|
||||||
|
|
||||||
<div class="set-group">
|
<div class="set-group">
|
||||||
<div class="set-group-head"><h4>Theme</h4><span class="set-scope">device</span></div>
|
<div class="set-group-head"><h4>Theme</h4><span class="set-scope">device</span></div>
|
||||||
@@ -2077,39 +2081,6 @@
|
|||||||
</optgroup>
|
</optgroup>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="set-row has-field" data-search="entrance animations motion tabs windows">
|
|
||||||
<div class="set-row-text">
|
|
||||||
<span class="set-row-label">Entrance Animations</span>
|
|
||||||
<span class="set-row-desc">How new tabs, panes and agent windows arrive. Add ?animlab=1 to the URL for per-surface control.</span>
|
|
||||||
</div>
|
|
||||||
<select id="appSettingsEntranceAnim" class="set-select">
|
|
||||||
<option value="legacy">Off (default)</option>
|
|
||||||
<option value="terminal">Terminal (CRT)</option>
|
|
||||||
<option value="beamdown">Beam down</option>
|
|
||||||
<option value="launch">Launch (tiles fly from tabs)</option>
|
|
||||||
<option value="softfocus">Soft focus (blur)</option>
|
|
||||||
<option value="quiet">Quiet</option>
|
|
||||||
<option value="playful">Playful</option>
|
|
||||||
<option value="custom">Custom (set in the lab)</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="set-row has-field" data-search="tile animations tiles grid motion entrance fly deal crt beam">
|
|
||||||
<div class="set-row-text">
|
|
||||||
<span class="set-row-label">Tile Animations</span>
|
|
||||||
<span class="set-row-desc">How tiles arrive when the grid opens and leave when it closes. A theme above presets it.</span>
|
|
||||||
</div>
|
|
||||||
<select id="appSettingsTileAnim" class="set-select">
|
|
||||||
<option value="settle">Off (default)</option>
|
|
||||||
<option value="fly">Fly from tab</option>
|
|
||||||
<option value="deal">Deal</option>
|
|
||||||
<option value="crt">CRT</option>
|
|
||||||
<option value="beam">Beam down</option>
|
|
||||||
<option value="cascade">Cascade</option>
|
|
||||||
<option value="pop">Pop</option>
|
|
||||||
<option value="soft">Soft</option>
|
|
||||||
<option value="off">None (tiles just appear)</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -2266,6 +2237,70 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- ══ Animations ═══════════════════════════════════════════════
|
||||||
|
Every entrance animation in one place (entrance-animations.js).
|
||||||
|
All per-device localStorage keys, applied as they are picked,
|
||||||
|
never part of PUT /api/settings. -->
|
||||||
|
<section class="set-section" id="settings-animations" data-label="Animations">
|
||||||
|
<div class="set-section-head">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3l1.8 4.7L18.5 9.5l-4.7 1.8L12 16l-1.8-4.7L5.5 9.5l4.7-1.8z"/><path d="M19 15l.8 2.2L22 18l-2.2.8L19 21l-.8-2.2L16 18l2.2-.8z"/><path d="M3 19h6"/></svg>
|
||||||
|
<h2>Animations</h2>
|
||||||
|
</div>
|
||||||
|
<p class="set-section-blurb">How tabs, terminal panes, agent windows and tiles arrive. All off by default, applied as you pick them.</p>
|
||||||
|
|
||||||
|
<div class="set-group">
|
||||||
|
<div class="set-group-head"><h4>Entrances</h4><span class="set-scope">device</span></div>
|
||||||
|
<div class="set-group-body">
|
||||||
|
<div class="set-row has-field" data-search="entrance animations theme motion tabs windows panes lines">
|
||||||
|
<div class="set-row-text">
|
||||||
|
<span class="set-row-label">Entrance Theme</span>
|
||||||
|
<span class="set-row-desc">One look for how new tabs, terminal panes, agent windows and their lines arrive.</span>
|
||||||
|
</div>
|
||||||
|
<select id="appSettingsEntranceAnim" class="set-select">
|
||||||
|
<option value="legacy">Off (default)</option>
|
||||||
|
<option value="terminal">Terminal (CRT)</option>
|
||||||
|
<option value="beamdown">Beam down</option>
|
||||||
|
<option value="launch">Launch (tiles fly from tabs)</option>
|
||||||
|
<option value="softfocus">Soft focus (blur)</option>
|
||||||
|
<option value="quiet">Quiet</option>
|
||||||
|
<option value="playful">Playful</option>
|
||||||
|
<option value="custom">Custom (set in the lab)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="set-row has-field" data-search="tile animations tiles grid motion entrance fly deal crt beam">
|
||||||
|
<div class="set-row-text">
|
||||||
|
<span class="set-row-label">Tile Animations</span>
|
||||||
|
<span class="set-row-desc">How tiles arrive when the grid opens and leave when it closes. A theme above presets it.</span>
|
||||||
|
</div>
|
||||||
|
<select id="appSettingsTileAnim" class="set-select">
|
||||||
|
<option value="settle">Off (default)</option>
|
||||||
|
<option value="fly">Fly from tab</option>
|
||||||
|
<option value="deal">Deal</option>
|
||||||
|
<option value="crt">CRT</option>
|
||||||
|
<option value="beam">Beam down</option>
|
||||||
|
<option value="cascade">Cascade</option>
|
||||||
|
<option value="pop">Pop</option>
|
||||||
|
<option value="soft">Soft</option>
|
||||||
|
<option value="off">None (tiles just appear)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="set-group">
|
||||||
|
<div class="set-group-head"><h4>Lab</h4></div>
|
||||||
|
<div class="set-group-body">
|
||||||
|
<div class="set-row has-field" data-search="animation lab compare replay styles per surface animlab">
|
||||||
|
<div class="set-row-text">
|
||||||
|
<span class="set-row-label">Animation Lab</span>
|
||||||
|
<span class="set-row-desc">Closes settings and opens every style per surface side by side, with replay and speed. Same as adding ?animlab=1 to the URL.</span>
|
||||||
|
</div>
|
||||||
|
<button type="button" id="appSettingsOpenAnimLab" class="btn-toolbar btn-sm">Open lab</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<!-- ══ Models ═══════════════════════════════════════════════════ -->
|
<!-- ══ Models ═══════════════════════════════════════════════════ -->
|
||||||
<section class="set-section" id="settings-models" data-label="Models">
|
<section class="set-section" id="settings-models" data-label="Models">
|
||||||
<div class="set-section-head">
|
<div class="set-section-head">
|
||||||
|
|||||||
@@ -20081,8 +20081,8 @@ body.tile-grid-resizing--row * {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ── Tile grid entrance styles (entrance-animations.js TILE_ANIM_STYLES) ───
|
/* ── Tile grid entrance styles (entrance-animations.js TILE_ANIM_STYLES) ───
|
||||||
Picked by the Entrance Animations theme (App Settings → Appearance) or per
|
Picked in App Settings → Animations (Tile Animations, or preset by the
|
||||||
surface in the lab (?animlab=1); `settle` is the rule above, the default,
|
Entrance Theme) or per surface in the lab (?animlab=1); `settle` is the rule above, the default,
|
||||||
and what a reload restores with. Any other style is held one frame
|
and what a reload restores with. Any other style is held one frame
|
||||||
(.tile--enter-hold: invisible, not animating) while the entrance module
|
(.tile--enter-hold: invisible, not animating) while the entrance module
|
||||||
orders the cascade and measures the tile's source on the final layout, then
|
orders the cascade and measures the tile's source on the final layout, then
|
||||||
|
|||||||
@@ -80,6 +80,31 @@ describe('App Settings modal structure', () => {
|
|||||||
expect(system).toContain('id="appSettingsTunnelEnabled"');
|
expect(system).toContain('id="appSettingsTunnelEnabled"');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Owner decision (2026-10-09): every animation setting has its own
|
||||||
|
* Animations section, right after Appearance, so it is easy to find. The
|
||||||
|
* selects are wired by id in entrance-animations.js, not by the load/save
|
||||||
|
* path above, so they get their own check here.
|
||||||
|
*/
|
||||||
|
it('keeps every animation setting in its own Animations section, after Appearance', () => {
|
||||||
|
const modal = settingsModal();
|
||||||
|
const rail = [...modal.matchAll(/data-section="([a-z-]+)"/g)].map((m) => m[1]);
|
||||||
|
const order = [...modal.matchAll(/<section class="set-section" id="([a-z-]+)"/g)].map((m) => m[1]);
|
||||||
|
for (const list of [rail, order]) {
|
||||||
|
expect(list[list.indexOf('settings-appearance') + 1]).toBe('settings-animations');
|
||||||
|
}
|
||||||
|
const animations = modal.match(/id="settings-animations"([\s\S]*?)<\/section>/)?.[1] ?? '';
|
||||||
|
for (const id of ['appSettingsEntranceAnim', 'appSettingsTileAnim', 'appSettingsOpenAnimLab']) {
|
||||||
|
expect(animations, `${id} belongs in the Animations section`).toContain(`id="${id}"`);
|
||||||
|
}
|
||||||
|
const appearance = modal.match(/id="settings-appearance"([\s\S]*?)<\/section>/)?.[1] ?? '';
|
||||||
|
expect(appearance).not.toMatch(/id="appSettings[A-Za-z]*Anim"/);
|
||||||
|
const anim = readFileSync(resolve(publicDir, 'entrance-animations.js'), 'utf8');
|
||||||
|
for (const id of ['appSettingsEntranceAnim', 'appSettingsTileAnim', 'appSettingsOpenAnimLab']) {
|
||||||
|
expect(anim).toContain(`document.getElementById('${id}')`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it('keeps Local Echo the first row of the second section', () => {
|
it('keeps Local Echo the first row of the second section', () => {
|
||||||
const terminal = settingsModal().match(/id="settings-terminal"([\s\S]*?)<\/section>/);
|
const terminal = settingsModal().match(/id="settings-terminal"([\s\S]*?)<\/section>/);
|
||||||
const localEcho = terminal?.[1].indexOf('appSettingsLocalEcho') ?? -1;
|
const localEcho = terminal?.[1].indexOf('appSettingsLocalEcho') ?? -1;
|
||||||
|
|||||||
Reference in New Issue
Block a user