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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/media/tui-lock-v2/40x12/session-shared.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 17 additions & 7 deletions docs/media/tui-lock-v2/SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ Design-only deliverable for the full TUI redesign. Layout language is taken
composer with the model chip in the bottom border, Settings modal with search +
categorised rows + tip/nav footer, slash autocomplete above the composer, effort
radios under `/model`, token counter top-right, footer shortcut strip) and
re-skinned to the Cortex chrome. Designer boards stay design-only; runtime
captures use `MinimalSessionView` plus lock flags (`computer_held`,
`show_computer_default`, `offline_held`, `rate_limit_held`) so MockTerminal
matches live chrome.
re-skinned to the Cortex chrome. Designer boards are the pixel target; matching
runtime chrome ships in `cortex-tui` (`lock_v2_*` scenes plus production paints
and lock flags: `computer_held`, `show_computer_default`, `offline_held`,
`rate_limit_held`, `share_link`) so MockTerminal captures can lock the live UI.

- Boards: [`index.md`](index.md) — **95** runtime boards at 120×40, 49 of them also at 40×12. Designer PNG files under `{40x12,120x40}/` from other PRs are not rewritten; new scenes are captured with `generate_tui_lock_screenshots --v2 --only`.
- Boards: [`index.md`](index.md) — **96** runtime boards at 120×40, 50 of them also at 40×12. Designer PNG files under `{40x12,120x40}/` from other PRs are not rewritten; new scenes are captured with `generate_tui_lock_screenshots --v2 --only`.
- Grids: `txt/<size>/<board>.txt` — the exact character grid of every board (diff a
`MockTerminal` capture against these).
- Renderer: `tools/render_lock_v2.py` + `tools/boards.py` (Python 3 + Pillow, IBM Plex Mono
Expand Down Expand Up @@ -98,7 +98,8 @@ Board geometry used by the renderer (documentation only — the product renders
```

Header-left is intentionally empty on launch. It never paints a shell echo
(`~/…`, `> cortex`). Optional: the session name after `/rename`, dim.
(`~/…`, `> cortex`). Optional, dim: the session name after `/rename`, or the
`Shared · read-only` marker while a `/share` link is live (`Shared` at 40 cols).

---

Expand All @@ -116,6 +117,13 @@ Legend for recipes: `T` text `#F5F5F5` · `D` dim `#6B7280` · `M` muted `#4B556
- ≥ 90 % of the window: counter turns `A` and the transcript gets one line
`Context is 92% full — /compact summarizes the thread to free room.` (`A` + `T` + `D`).

```
Shared · read-only 14K / 500K ← marker D, left margin col 1
```
- Persistent status chrome while a read-only `/share` link is live — `D`, never
accent, never a wash. Clipped before the counter; `Shared` alone at 40 cols.
- `/unshare` clears the marker; the link line stays in the transcript as history.

### 3.2 User prompt bar

```
Expand Down Expand Up @@ -409,6 +417,7 @@ Narrow: no bars, `used / total pct%`.
|---|---|
| welcome-cortex / welcome-agent / session-empty | `welcome-cortex`, `welcome-agent`, `session-empty` (+ `first-run-tips`) |
| session-user-bars / thought / assistant / worked / optin | `session-user-bars`, `session-thought`, `session-thought-expanded`, `session-thinking-live`, `session-assistant`, `session-worked`, `session-optin`, `session-optin-hover` |
| `/share` link live (status-line marker) · `/unshare` clears it | `session-shared` |
| composer-empty / typing (+blink) / hover | `composer-empty`, `composer-typing`, `composer-typing-blink`, `composer-hover`, `composer-multiline` |
| footer-shortcuts / tokens-topright | `footer-shortcuts`, `footer-hover`, `tokens-topright`, `tokens-topright-warn` |
| slash-palette / slash-model-typed | `slash-palette` (`/goal` after `/plan`), `slash-model-typed` |
Expand All @@ -426,7 +435,8 @@ Narrow: no bars, `used / total pct%`.
| local-tools consent · @file chip · undo/redo/rewind | `consent-local-tools`, `composer-file-chip`, `undo-sheet` |

Narrow (40×12) set: `welcome-cortex`, `welcome-agent`, `first-run-tips`, `session-empty`, `session-user-bars`,
`session-thinking-live`, `session-assistant`, `session-optin`, `composer-empty`, `composer-typing`, `composer-hover`,
`session-thinking-live`, `session-assistant`, `session-optin`, `session-shared`, `composer-empty`, `composer-typing`,
`composer-hover`,
`tokens-topright`, `compact-chat`, `slash-palette`, `goal-chip-active`, `goal-chip-paused`, `goal-chip-done`,
`goal-chip-budget`, `goal-chip-blocked`, `slash-model-typed`, `model-list`, `model-effort-high`,
`settings-appearance`, `settings-mouse`, `settings-row-hover`, `settings-theme-submenu`, `theme-picker`, `session-fork`, `init-agents`, `custom-commands`, `hooks-lifecycle`, `handoff-confirm`, `mode-plan`, `mode-ask`,
Expand Down
5 changes: 4 additions & 1 deletion docs/media/tui-lock-v2/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Regenerate: `python3 tools/render_lock_v2.py --index` (fetches IBM Plex Mono on
| `session-worked` | `Worked for Xs` after a reply | [120x40](120x40/session-worked.png) | — |
| `session-optin` | `Help improve Cortex` banner — Opt out | Opt in | [120x40](120x40/session-optin.png) | [40x12](40x12/session-optin.png) |
| `session-optin-hover` | Banner with the mouse over `[Opt in]` | [120x40](120x40/session-optin-hover.png) | — |
| `session-shared` | `/share` link live — dim `Shared` marker in the status line | [120x40](120x40/session-shared.png) | [40x12](40x12/session-shared.png) |
| `composer-empty` | Empty composer — caret before the placeholder, banner green `>` | [120x40](120x40/composer-empty.png) | [40x12](40x12/composer-empty.png) |
| `composer-typing` | Mid-type, caret on | [120x40](120x40/composer-typing.png) | [40x12](40x12/composer-typing.png) |
| `composer-typing-blink` | Mid-type, caret off (blink phase) | [120x40](120x40/composer-typing-blink.png) | — |
Expand Down Expand Up @@ -126,4 +127,6 @@ Regenerate: `python3 tools/render_lock_v2.py --index` (fetches IBM Plex Mono on
| `composer-file-chip` | Composer with attached @file chip in prompt | [120x40](120x40/composer-file-chip.png) | [40x12](40x12/composer-file-chip.png) |
| `undo-sheet` | /undo /redo /rewind sheet | [120x40](120x40/undo-sheet.png) | [40x12](40x12/undo-sheet.png) |

**95** runtime boards at 120x40 · **49** at 40x12. Designer PNG pack from other PRs is unchanged; new COR-18/225/227/228 scenes are runtime captures. Computer lock boards from #69 stay in the pack.
**96** runtime boards at 120x40 · **50** at 40x12. Designer PNG pack from other PRs is unchanged; new COR-18/225/227/228 scenes are runtime captures. Computer lock boards from #69 stay in the pack. `session-shared` (COR-226) is at both sizes.

Runtime MockTerminal pack (same ids): [`runtime/120x40`](runtime/120x40/) (96) · [`runtime/40x12`](runtime/40x12/) (50).
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 4 additions & 3 deletions docs/media/tui-lock-v2/runtime/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ is banner green `#1F4945`; historical violet `#A78BFA` is not the lock.
Designer boards (pixel target) live in `docs/media/tui-lock-v2/{40x12,120x40}/`.
These runtime frames are what Designer cli signs off against.

SPEC §7: **95** boards at 120×40 and **49** at 40×12. Each filename is one
SPEC §7: **96** boards at 120×40 and **50** at 40×12. Each filename is one
distinct live state — no two PNGs share a sha256. Includes `/goal` composer
chips (`goal-chip-*`), distinct `offline` and `rate-limit` diagnostics,
Computer lock boards (`computer-disconnected`, `computer-cloud-default`),
local-tools consent, composer `@file` chip, `/undo` `/redo` `/rewind` sheet,
and runtime-only COR-18/225/227/228 scenes (`theme-picker`, `handoff-confirm`,
`session-fork`, `init-agents`, `custom-commands`, `hooks-lifecycle`).
runtime-only COR-18/225/227/228 scenes (`theme-picker`, `handoff-confirm`,
`session-fork`, `init-agents`, `custom-commands`, `hooks-lifecycle`),
and the `session-shared` status-line marker while a read-only `/share` link is live.
23 changes: 23 additions & 0 deletions docs/media/tui-lock-v2/tools/boards.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@
PLACEHOLDER = "Plan, search, build anything"
TOKENS = "14K / 500K"

# `/share` chrome — the marker is dim status chrome and stays up until `/unshare`.
SHARE_MARKER = "Shared · read-only"
SHARE_MARKER_NARROW = "Shared"
SHARE_TITLE = "Shared this session — read-only link is live."
SHARE_TITLE_NARROW = "Shared — read-only link is live."
SHARE_LINK = "cortex.foundation/share/9f4c2a71 · copied to your clipboard"
SHARE_LINK_NARROW = "cortex.foundation/share/9f4c2a71"
SHARE_SCOPE = "Anyone with the link can read this session. /unshare turns it off."


@dataclass
class Ctx:
Expand Down Expand Up @@ -774,6 +783,19 @@ def board_session_optin_hover(s, c):
footer(s, c)


def board_session_shared(s, c):
"""`/share` is live — dim `Shared` marker in the status line, idle session chrome."""
header(s, c, left=SHARE_MARKER_NARROW if c.narrow else SHARE_MARKER)
top = composer(s, c)
f = Flow(s, c, limit=top - 1)
f.user("/share", "04:41 PM")
f.dim(SHARE_TITLE_NARROW if c.narrow else SHARE_TITLE)
f.dim(SHARE_LINK_NARROW if c.narrow else SHARE_LINK, indent=2)
if not c.narrow:
f.dim(SHARE_SCOPE, indent=2)
footer(s, c)


def board_composer_empty(s, c):
header(s, c)
composer(s, c)
Expand Down Expand Up @@ -2016,6 +2038,7 @@ def board_undo_sheet(s, c):
("session-worked", board_session_worked, False, "B", "`Worked for Xs` after a reply"),
("session-optin", board_session_optin, True, "B", "`Help improve Cortex` banner — Opt out | Opt in"),
("session-optin-hover", board_session_optin_hover, False, "B", "Banner with the mouse over `[Opt in]`"),
("session-shared", board_session_shared, True, "B", "`/share` link live — dim `Shared` marker in the status line"),
("composer-empty", board_composer_empty, True, "B", "Empty composer — caret before the placeholder, banner green `>`"),
("composer-typing", board_composer_typing, True, "B", "Mid-type, caret on"),
("composer-typing-blink", board_composer_typing_blink, False, "B", "Mid-type, caret off (blink phase)"),
Expand Down
40 changes: 40 additions & 0 deletions docs/media/tui-lock-v2/txt/120x40/session-shared.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
Shared · read-only 14K / 500K

> /share 04:41 PM

Shared this session — read-only link is live.
cortex.foundation/share/9f4c2a71 · copied to your clipboard
Anyone with the link can read this session. /unshare turns it off.




























╭─ Agent ────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ > Plan, search, build anything │
╰─────────────────────────────────────────────────────────────────────────────────────────── Cortex Mini 1 (medium) ─╯

Shift+Tab:mode | Ctrl+x:shortcuts
12 changes: 12 additions & 0 deletions docs/media/tui-lock-v2/txt/40x12/session-shared.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Shared 14K / 500K

> /share

Shared — read-only link is live.
cortex.foundation/share/9f4c2a71

╭─ Agent ────────────────────────────╮
│ > Plan, search, build anything │
╰─────────── Cortex Mini 1 (medium) ─╯

Shift+Tab:mode | Ctrl+x:shortcuts
9 changes: 5 additions & 4 deletions scripts/render-tui-lock-v2.sh
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ for spec in 40x12 120x40; do
python3 scripts/ansi-frames-to-gif.py --frames "$frames" --png-only "$pngs"
done

unique_pngs "$output_dir/40x12" 49
unique_pngs "$output_dir/120x40" 95
unique_pngs "$output_dir/40x12" 50
unique_pngs "$output_dir/120x40" 96

python3 - "$output_dir" <<'PY'
from pathlib import Path
Expand Down Expand Up @@ -100,12 +100,13 @@ is banner green `#1F4945`; historical violet `#A78BFA` is not the lock.
Designer boards (pixel target) live in `docs/media/tui-lock-v2/{40x12,120x40}/`.
These runtime frames are what Designer cli signs off against.

SPEC §7: **95** boards at 120×40 and **49** at 40×12. Each filename is one
SPEC §7: **96** boards at 120×40 and **50** at 40×12. Each filename is one
distinct live state — no two PNGs share a sha256. Includes `/goal` composer
chips (`goal-chip-*`), distinct `offline` and `rate-limit` diagnostics,
Computer lock boards (`computer-disconnected`, `computer-cloud-default`),
local-tools consent, composer `@file` chip, `/undo` `/redo` `/rewind` sheet,
and runtime-only COR-18/225/227/228 scenes.
runtime-only COR-18/225/227/228 scenes,
and the `session-shared` status-line marker while a read-only `/share` link is live.

EOF

Expand Down
18 changes: 12 additions & 6 deletions src/cortex-core/src/animation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,23 +121,29 @@ mod tests {

#[test]
fn test_fade_in() {
let fade = Fade::fade_in(100);
// Long duration so a loaded nextest worker cannot advance past mid-fade
// between construction and the first progress() sample.
let fade = Fade::fade_in(60_000);
assert!(!fade.is_complete());
assert_eq!(fade.direction(), FadeDirection::In);

// Initial progress should be near 0
let initial = fade.progress();
assert!(initial < 0.5);
assert!(
initial < 0.5,
"fresh fade-in should start near 0, got {initial}"
);
}

#[test]
fn test_fade_out() {
let fade = Fade::fade_out(100);
let fade = Fade::fade_out(60_000);
assert_eq!(fade.direction(), FadeDirection::Out);

// Initial progress should be near 1 (since it's fading out)
let initial = fade.progress();
assert!(initial > 0.5);
assert!(
initial > 0.5,
"fresh fade-out should start near 1, got {initial}"
);
}

#[test]
Expand Down
3 changes: 3 additions & 0 deletions src/cortex-tui/src/app/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,8 @@ pub struct AppState {
pub computer_held: bool,
/// Welcome info card should lock-paint Computer · Cloud as the shipped default.
pub show_computer_default: bool,
/// Live read-only share link minted by `/share`; `None` once `/unshare` clears it.
pub share_link: Option<String>,
/// Launched via the `agent` binary / alias.
pub agent_entrypoint: bool,
/// Token counter used / window.
Expand Down Expand Up @@ -354,6 +356,7 @@ impl AppState {
rate_limit_held: false,
computer_held: false,
show_computer_default: false,
share_link: None,
agent_entrypoint: false,
tokens_used: 0,
context_window: 500_000,
Expand Down
1 change: 1 addition & 0 deletions src/cortex-tui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ mod lock_v2_ids;
mod lock_v2_network;
mod lock_v2_parity;
mod lock_v2_scenes;
mod lock_v2_share;
pub mod readme_hero;
pub mod readme_hero_boards;
pub mod splash_chrome;
Expand Down
4 changes: 2 additions & 2 deletions src/cortex-tui/src/lock_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,8 @@ mod tests {

#[test]
fn lock_v2_wide_count_is_spec() {
assert_eq!(LOCK_V2_WIDE_IDS.len(), 95);
assert_eq!(LOCK_V2_NARROW_IDS.len(), 49);
assert_eq!(LOCK_V2_WIDE_IDS.len(), 96);
assert_eq!(LOCK_V2_NARROW_IDS.len(), 50);
}

#[test]
Expand Down
2 changes: 2 additions & 0 deletions src/cortex-tui/src/lock_v2_boards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use crate::lock_v2_goal::{
use crate::lock_v2_network::apply_offline_rate_limit_scene;
use crate::lock_v2_parity::apply_parity_scene;
use crate::lock_v2_scenes::*;
use crate::lock_v2_share::apply_share_scene;
use crate::modal::mcp_manager::{McpServerInfo, McpStatus};
use crate::session::SessionSummary;
use crate::ui::consts::SERVICE_UNAVAILABLE_NEXT_STEP;
Expand Down Expand Up @@ -814,6 +815,7 @@ Tell me what you'd like to do.",
id if apply_offline_rate_limit_scene(id, &mut state) => {}
id if apply_parity_scene(id, &mut state, width) => {}
id if apply_designed_scene(id, &mut state, width) => {}
id if apply_share_scene(id, &mut state) => {}
id if apply_goal_chip_scene(id, &mut state) => {}
"computer-disconnected" | "computer-cloud-default" => {
assert!(
Expand Down
4 changes: 2 additions & 2 deletions src/cortex-tui/src/lock_v2_computer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ mod tests {
assert!(LOCK_V2_WIDE_IDS.contains(id), "{id} missing from wide");
assert!(LOCK_V2_NARROW_IDS.contains(id), "{id} missing from narrow");
}
assert_eq!(LOCK_V2_WIDE_IDS.len(), 95);
assert_eq!(LOCK_V2_NARROW_IDS.len(), 49);
assert_eq!(LOCK_V2_WIDE_IDS.len(), 96);
assert_eq!(LOCK_V2_NARROW_IDS.len(), 50);
let mut seen = std::collections::HashSet::new();
for id in LOCK_V2_WIDE_IDS.iter().chain(LOCK_V2_NARROW_IDS) {
seen.insert(*id);
Expand Down
4 changes: 2 additions & 2 deletions src/cortex-tui/src/lock_v2_designed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,8 +246,8 @@ mod tests {
}
assert!(!LOCK_V2_WIDE_IDS.contains(&"share-link"));
assert!(!LOCK_V2_WIDE_IDS.contains(&"unshare"));
assert_eq!(LOCK_V2_WIDE_IDS.len(), 95);
assert_eq!(LOCK_V2_NARROW_IDS.len(), 49);
assert_eq!(LOCK_V2_WIDE_IDS.len(), 96);
assert_eq!(LOCK_V2_NARROW_IDS.len(), 50);
}

#[test]
Expand Down
10 changes: 6 additions & 4 deletions src/cortex-tui/src/lock_v2_ids.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Lock v2 scene id lists. Split out of [`crate::lock_v2`] so adding `/goal`
//! boards does not grow that file past the source-policy line-count baseline.

/// Narrow (40×12) SPEC §7 set — 49 boards.
/// Narrow (40×12) SPEC §7 set — 50 boards.
pub const LOCK_V2_NARROW_IDS: &[&str] = &[
"welcome-cortex",
"welcome-agent",
Expand All @@ -11,6 +11,7 @@ pub const LOCK_V2_NARROW_IDS: &[&str] = &[
"session-thinking-live",
"session-assistant",
"session-optin",
"session-shared",
"composer-empty",
"composer-typing",
"composer-hover",
Expand Down Expand Up @@ -54,7 +55,7 @@ pub const LOCK_V2_NARROW_IDS: &[&str] = &[
"undo-sheet",
];

/// Wide (120×40) SPEC §7 set — 95 boards.
/// Wide (120×40) SPEC §7 set — 96 boards.
pub const LOCK_V2_WIDE_IDS: &[&str] = &[
"welcome-cortex",
"welcome-agent",
Expand All @@ -68,6 +69,7 @@ pub const LOCK_V2_WIDE_IDS: &[&str] = &[
"session-worked",
"session-optin",
"session-optin-hover",
"session-shared",
"composer-empty",
"composer-typing",
"composer-typing-blink",
Expand Down Expand Up @@ -169,8 +171,8 @@ mod tests {

#[test]
fn lock_v2_id_counts_and_unique() {
assert_eq!(LOCK_V2_WIDE_IDS.len(), 95);
assert_eq!(LOCK_V2_NARROW_IDS.len(), 49);
assert_eq!(LOCK_V2_WIDE_IDS.len(), 96);
assert_eq!(LOCK_V2_NARROW_IDS.len(), 50);
let mut wide = HashSet::new();
for id in LOCK_V2_WIDE_IDS {
assert!(wide.insert(*id), "duplicate wide id {id}");
Expand Down
Loading