M0: compilable skeleton — Kigi 0.1.0 fork surgery
Hard fork of xai-org/grok-build (Apache-2.0) re-targeted as Kigi, an
unofficial Kimi Code CLI community build.
Rename & identity
- 72 xai-*/xai-grok-* crates -> kigi-* (explicit: xai-grok-pager-bin ->
kigi-bin [binary `kigi`], xai-grok-pager -> kigi-tui; rest mechanical);
ptyctl, ptyctl-cli, third_party/ unchanged; proto package
xai.grok.tools.v1 -> kigi.tools.v1
- Config home ~/.kigi (KIGI_SHARE_DIR override), env prefix GROK_* ->
KIGI_*, `kigi --version` carries the unofficial-community-build notice
- clap identity, help text, startup banner, prompt templates rebranded
(templates re-encrypted)
Deletions (PRD removal list #5/#6/#7/#9/#10)
- voice input (xai-grok-voice) and all TUI wiring
- telemetry: Mixpanel client, external OTel stream, Sentry, OTLP layers,
trace/GCS/S3 upload queues (kigi-file-utils halved), workspace upload
module & dc_log, heap-profile uploader, auth-diagnostics uploader,
session-analytics halves of feedback; local zero-egress observability
preserved in new kigi-log crate (unified log, --debug firehose,
subsystem file logs, opt-in instrumentation)
- announcements (crate, remote-settings fields, TUI surfaces)
- plugin marketplace (crate, sources/browse/CTA/extensions-modal tab);
direct plugin install/uninstall/update via kigi-agent git_install kept
- relay/gateway/assets endpoints and features (agent relay, headless
relay transport, gateway bridge, LeaderEnvUrls); leader IPC socket now
~/.kigi/leader.sock + KIGI_LEADER_SOCKET, no ws-url derivation
- functional types rehomed instead of deleted: PermissionMode ->
kigi-config-types, McpInitStrategy -> kigi-mcp, PrCreationSource ->
session signals, TerminalDiagnostics -> kigi-pager-render, agent_id ->
shell util
Endpoints
- kigi-env rewritten: single production KigiEndpoints {coding_api_base_url
https://api.kimi.com/coding/v1 (KIGI_CODE_BASE_URL), oauth_host
https://auth.kimi.com (KIGI_OAUTH_HOST), update_base_url (GitHub
Releases API), upgrade_page_url}; GrokBuildEnvironment enum deleted
Toolchain & workspace hygiene
- Rust 1.97.0 pinned; edition 2024; full cargo update; git2 hoisted to
workspace at 0.21 (Option->Result API migration), quick-xml 0.41
- Root Cargo.toml hand-maintained (PRD §8.1): version 0.1.0 inherited by
all members, members sorted, unused deps pruned
- cargo-deny advisories gate (deny.toml with documented transitive
exceptions); CI workflow (check/clippy/fmt/deny/test, macOS+Linux)
- cross-crate test seams re-gated behind `test-support` cargo feature;
insta snapshot baselines renamed to the kigi_tui prefix
- clippy --workspace --all-targets: zero warnings; fmt clean
Fixes surfaced by the port
- updater probe/installer divergence (bin/kigi vs bin/grok symlink set)
- idle model-metadata refresh dead under KIGI_CODE_BASE_URL override
(new is_effective_coding_endpoint_url, loopback+override aware)
- macOS symlinked-TMPDIR fixture canonicalization (foreign_sessions,
fast-worktree); RSS measurement tests serialized via serial_test
Docs & legal (Apache §4)
- NOTICE added (upstream attribution + change statement); THIRD-PARTY
notices sustained; kigi-tools ported-code notices extended; README,
CONTRIBUTING, SECURITY, AGENTS.md rewritten
Out of scope for M0 (tracked): Kimi auth/inference (M1), search/fetch,
command parity, config import (M2), Computer Hub excision & final
brand-token sweep (M2), distribution & self-update rewrite (M3).
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
name: ansi-execute-output
|
||||
description: >-
|
||||
A user `!` bash command whose stdout contains ANSI SGR renders styled plain
|
||||
text in the execute scrollback. The filenames appear cleanly in the stdout
|
||||
body (gutter-prefixed) rather than as literal escape sequences.
|
||||
terminal:
|
||||
rows: 45
|
||||
cols: 110
|
||||
mock:
|
||||
response: "BASH_SESSION_READY"
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 25000
|
||||
- action: type_text
|
||||
text: "go"
|
||||
- action: keys
|
||||
keys: "<Enter>"
|
||||
- action: wait_for_text
|
||||
text: BASH_SESSION_READY
|
||||
timeout_ms: 45000
|
||||
- action: paste_prompt
|
||||
text: "! printf '\\033[1m\\033[36mbazel\\033[39;49m\\033[0m\\nAGENTS.md\\n'"
|
||||
- action: keys
|
||||
keys: "<Enter>"
|
||||
- action: wait_for_text
|
||||
text: "Run (user)"
|
||||
timeout_ms: 30000
|
||||
- action: wait_for_text
|
||||
text: bazel
|
||||
timeout_ms: 20000
|
||||
- action: wait
|
||||
millis: 500
|
||||
# Stdout body shows clean filenames (gutter-prefixed), proving SGR was rendered
|
||||
# natively rather than printed as literal `[1m[36m...` escape codes.
|
||||
- action: assert_contains
|
||||
text: "┃ bazel"
|
||||
- action: assert_contains
|
||||
text: "┃ AGENTS.md"
|
||||
- action: screenshot
|
||||
name: ansi-execute-native-render
|
||||
note: Bash stdout shows styled bazel/AGENTS.md (SGR rendered, no literal escapes).
|
||||
@@ -0,0 +1,143 @@
|
||||
name: auto-compact-resize
|
||||
description: >
|
||||
Auto-compact on small terminals: shrinking to 18 rows (above the 16-row
|
||||
short-terminal trims, at or below the 20-row auto-compact threshold) renders
|
||||
the UI as if compact mode were on — the sticky prev-question header
|
||||
(compact-disabled chrome) disappears — without persisting anything; growing
|
||||
back to 32 rows restores it. The user setting never changes across the run.
|
||||
terminal:
|
||||
rows: 32
|
||||
cols: 100
|
||||
environment:
|
||||
env:
|
||||
# Pin the contextual hints master OFF so no ephemeral tip can land in the
|
||||
# banner row and confuse the text assertions.
|
||||
- key: KIGI_CONTEXTUAL_HINTS
|
||||
value: "0"
|
||||
mock:
|
||||
# Blank-line-separated paragraphs render one row each (no soft-wrap merge),
|
||||
# so the transcript is ~40 rows tall and the question scrolls off on every
|
||||
# terminal size this scenario uses — the sticky header is the only place
|
||||
# the prompt text can appear.
|
||||
response: |
|
||||
TOPLINE_AAA
|
||||
|
||||
filler line 01 keeps the transcript tall
|
||||
|
||||
filler line 02 keeps the transcript tall
|
||||
|
||||
filler line 03 keeps the transcript tall
|
||||
|
||||
filler line 04 keeps the transcript tall
|
||||
|
||||
filler line 05 keeps the transcript tall
|
||||
|
||||
filler line 06 keeps the transcript tall
|
||||
|
||||
filler line 07 keeps the transcript tall
|
||||
|
||||
filler line 08 keeps the transcript tall
|
||||
|
||||
filler line 09 keeps the transcript tall
|
||||
|
||||
filler line 10 keeps the transcript tall
|
||||
|
||||
filler line 11 keeps the transcript tall
|
||||
|
||||
filler line 12 keeps the transcript tall
|
||||
|
||||
filler line 13 keeps the transcript tall
|
||||
|
||||
filler line 14 keeps the transcript tall
|
||||
|
||||
filler line 15 keeps the transcript tall
|
||||
|
||||
filler line 16 keeps the transcript tall
|
||||
|
||||
filler line 17 keeps the transcript tall
|
||||
|
||||
filler line 18 keeps the transcript tall
|
||||
|
||||
filler line 19 keeps the transcript tall
|
||||
|
||||
filler line 20 keeps the transcript tall
|
||||
|
||||
filler line 21 keeps the transcript tall
|
||||
|
||||
filler line 22 keeps the transcript tall
|
||||
|
||||
filler line 23 keeps the transcript tall
|
||||
|
||||
filler line 24 keeps the transcript tall
|
||||
|
||||
filler line 25 keeps the transcript tall
|
||||
|
||||
filler line 26 keeps the transcript tall
|
||||
|
||||
filler line 27 keeps the transcript tall
|
||||
|
||||
filler line 28 keeps the transcript tall
|
||||
|
||||
filler line 29 keeps the transcript tall
|
||||
|
||||
filler line 30 keeps the transcript tall
|
||||
|
||||
filler line 31 keeps the transcript tall
|
||||
|
||||
filler line 32 keeps the transcript tall
|
||||
|
||||
filler line 33 keeps the transcript tall
|
||||
|
||||
filler line 34 keeps the transcript tall
|
||||
|
||||
filler line 35 keeps the transcript tall
|
||||
|
||||
filler line 36 keeps the transcript tall
|
||||
|
||||
BOTTOMLINE_QQQ
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: focus_prompt
|
||||
# The first char promotes the welcome prompt to a real session; Enter submits.
|
||||
- action: type_text
|
||||
text: "stickyprobe"
|
||||
- action: keys
|
||||
keys: "<Enter>"
|
||||
- action: wait_for_text
|
||||
text: BOTTOMLINE_QQQ
|
||||
timeout_ms: 30000
|
||||
- action: wait
|
||||
millis: 500
|
||||
# Pinned to the bottom of a ~40-row response: the question is scrolled off,
|
||||
# so "stickyprobe" on screen means the sticky prev-question header is up.
|
||||
- action: assert_contains
|
||||
text: "stickyprobe"
|
||||
# Shrink to the 20-row auto-compact threshold zone: auto-compact engages
|
||||
# and the sticky header (disabled in compact mode) vanishes.
|
||||
- action: resize
|
||||
rows: 18
|
||||
cols: 100
|
||||
- action: wait
|
||||
millis: 700
|
||||
- action: assert_running
|
||||
- action: assert_not_contains
|
||||
text: "stickyprobe"
|
||||
- action: screenshot
|
||||
name: auto-compact-engaged
|
||||
note: 18 rows — sticky prev-question header suppressed by auto-compact.
|
||||
# Grow back past the threshold: the derived value reverts to the user's
|
||||
# (non-compact) setting and the sticky header returns.
|
||||
- action: resize
|
||||
rows: 32
|
||||
cols: 100
|
||||
- action: wait
|
||||
millis: 700
|
||||
- action: assert_contains
|
||||
text: "stickyprobe"
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
- action: screenshot
|
||||
name: auto-compact-restored
|
||||
note: Back at 32 rows — sticky prev-question header restored.
|
||||
@@ -0,0 +1,27 @@
|
||||
name: bracket-prompt-input
|
||||
description: >
|
||||
Regression — typing a lone `[` in the prompt must render immediately
|
||||
(CsiFragmentFilter must not hold it until the next keystroke).
|
||||
terminal:
|
||||
rows: 32
|
||||
cols: 100
|
||||
mock:
|
||||
response: "unused"
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: focus_prompt
|
||||
- action: type_text
|
||||
text: "["
|
||||
- action: wait
|
||||
millis: 100
|
||||
- action: assert_contains
|
||||
text: "["
|
||||
- action: type_text
|
||||
text: "link]"
|
||||
- action: assert_contains
|
||||
text: "[link]"
|
||||
- action: screenshot
|
||||
name: bracket-prompt
|
||||
note: Prompt shows typed brackets without waiting for a follow-up key.
|
||||
@@ -0,0 +1,27 @@
|
||||
name: copy-selection
|
||||
description: Validate mouse text selection copies through the real TUI clipboard path with OSC 52 readback.
|
||||
terminal:
|
||||
rows: 34
|
||||
cols: 100
|
||||
environment:
|
||||
env:
|
||||
- key: SSH_CONNECTION
|
||||
value: "scripted-test 1 127.0.0.1 2"
|
||||
mock:
|
||||
response: "unused"
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: paste_prompt
|
||||
text: "copy source text"
|
||||
- action: assert_contains
|
||||
text: "copy source text"
|
||||
- action: select_text_range
|
||||
from_text: "copy"
|
||||
to_text: "text"
|
||||
- action: assert_osc52_contains
|
||||
text: "source text"
|
||||
- action: screenshot
|
||||
name: copied-selection
|
||||
note: Prompt text selected by mouse drag and copied via OSC 52.
|
||||
@@ -0,0 +1,47 @@
|
||||
name: dashboard-model-list-click
|
||||
description: >
|
||||
On the agent dashboard, open the /model completion list and mouse-click a
|
||||
model row. The click must select the model into the dispatch prompt and must
|
||||
NOT attach the underlying session row (click-through regression).
|
||||
terminal:
|
||||
rows: 50
|
||||
cols: 120
|
||||
environment:
|
||||
args:
|
||||
- dashboard
|
||||
env:
|
||||
- key: KIGI_AGENT_DASHBOARD
|
||||
value: "1"
|
||||
mock:
|
||||
response: "UNUSED_DASHBOARD_MODEL_CLICK"
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 30000
|
||||
# Dashboard header / list chrome — tolerate either branding string.
|
||||
- action: wait_for_text
|
||||
text: Agent
|
||||
timeout_ms: 20000
|
||||
# Open the /model arg suggestion list on the dispatch prompt.
|
||||
# Mock server exposes the default `test-model` catalog entry.
|
||||
- action: type_text
|
||||
text: "/model "
|
||||
- action: wait_for_text
|
||||
text: test-model
|
||||
timeout_ms: 15000
|
||||
# Click the model row in the dropdown (not the session list under it).
|
||||
- action: click_text
|
||||
text: test-model
|
||||
- action: wait
|
||||
millis: 400
|
||||
# Completion accepted into the dispatch input (model id inserted as args).
|
||||
- action: assert_contains
|
||||
text: test-model
|
||||
# Still on the dashboard — not inside a session overlay attach.
|
||||
- action: assert_contains
|
||||
text: Agent
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
- action: screenshot
|
||||
name: dashboard-model-selected-no-attach
|
||||
note: Model list mouse click selected test-model without attaching a session.
|
||||
@@ -0,0 +1,61 @@
|
||||
name: folder-trust-prompt
|
||||
description: >
|
||||
Full session e2e for the client-side folder-trust prompt. With the feature on
|
||||
(`KIGI_FOLDER_TRUST=1`) and the pager running in a git repo that ships a
|
||||
repo-local `.mcp.json`, `decide` returns `Prompt` so the trust question renders
|
||||
BEFORE any session starts. Accepting it (`y`) persists the grant, lets the
|
||||
session proceed, and a submitted prompt streams the mock assistant response —
|
||||
proving repo-local loading was gated until the user answered.
|
||||
terminal:
|
||||
rows: 50
|
||||
cols: 120
|
||||
environment:
|
||||
env:
|
||||
- key: KIGI_FOLDER_TRUST
|
||||
value: "1"
|
||||
# A self-built (unstamped) grok auto-trusts and never prompts; simulate a
|
||||
# release build so the folder-trust prompt is actually evaluated.
|
||||
- key: KIGI_TEST_VERSION
|
||||
value: "0.0.0-sim"
|
||||
workspace:
|
||||
git_init: true
|
||||
files:
|
||||
".mcp.json": "{\"mcpServers\": {}}"
|
||||
mock:
|
||||
response: "FOLDER_TRUST_SESSION_OK assistant reply streamed after the folder was trusted."
|
||||
steps:
|
||||
# 1. The trust question renders before any session exists.
|
||||
- action: wait_for_text
|
||||
text: "Do you trust the contents of this directory"
|
||||
timeout_ms: 20000
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
# The decline affordance is on screen (keyboard-driven y/N answer; n = quit).
|
||||
- action: assert_contains
|
||||
text: "No, quit"
|
||||
# 2. Accept the folder.
|
||||
- action: keys
|
||||
keys: "y"
|
||||
# 3. The question is gone and the normal welcome / prompt is ready.
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: assert_not_contains
|
||||
text: "Do you trust the contents of this directory"
|
||||
# 4. Submit a prompt — the session must now start (gate answered).
|
||||
- action: type_text
|
||||
text: "summarize the trusted repo"
|
||||
- action: keys
|
||||
keys: "<Enter>"
|
||||
# 5. The mock assistant response renders ⇒ the session actually started and
|
||||
# repo-local loading proceeded ungated.
|
||||
- action: wait_for_text
|
||||
text: FOLDER_TRUST_SESSION_OK
|
||||
timeout_ms: 30000
|
||||
- action: assert_chat_completion
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
- action: assert_running
|
||||
- action: screenshot
|
||||
name: folder-trust-session
|
||||
note: "Trust prompt accepted; prompt submitted; mock assistant response rendered."
|
||||
@@ -0,0 +1,39 @@
|
||||
name: goal-slash-presession
|
||||
description: >
|
||||
Regression for the enterprise deploy report: with the goal
|
||||
feature flag on (`KIGI_GOAL=1`), `/goal` must appear in the slash-command menu
|
||||
on the welcome screen *before* the first user turn creates a session. Pre-fix
|
||||
the pre-session advertise paths fail-closed and only emit AlwaysOn commands,
|
||||
so `/goal` showed up only after the first turn. Typing `/goal` on the welcome
|
||||
screen must surface the command (and its description) in the dropdown.
|
||||
terminal:
|
||||
rows: 40
|
||||
cols: 100
|
||||
environment:
|
||||
env:
|
||||
- key: KIGI_GOAL
|
||||
value: "1"
|
||||
mock:
|
||||
response: "unused — this scenario never submits a prompt."
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
# Sanity: nothing has been submitted yet, so no session exists.
|
||||
- action: assert_not_contains
|
||||
text: autonomous goal
|
||||
- action: focus_prompt
|
||||
- action: type_text
|
||||
text: "/goal"
|
||||
- action: wait
|
||||
millis: 300
|
||||
# The dropdown row carries the builtin's description, which only renders when
|
||||
# /goal is actually advertised pre-session.
|
||||
- action: assert_contains
|
||||
text: autonomous goal
|
||||
- action: assert_running
|
||||
- action: screenshot
|
||||
name: goal-in-slash-menu-presession
|
||||
note: "/goal advertised in the slash menu on the welcome screen (KIGI_GOAL=1), before any prompt."
|
||||
@@ -0,0 +1,47 @@
|
||||
name: goal-slash-presession-disabled
|
||||
description: >
|
||||
Counterpart to goal-slash-presession: with the goal feature flag explicitly
|
||||
OFF (`KIGI_GOAL=0` — goal mode now defaults on), `/goal` must NOT appear in
|
||||
the pre-session slash menu — the gate stays fail-closed. The menu itself
|
||||
still works pre-session: an always-on builtin like `/compact` shows up,
|
||||
proving the absence of `/goal` is the gate and not a broken dropdown.
|
||||
terminal:
|
||||
rows: 40
|
||||
cols: 100
|
||||
environment:
|
||||
env:
|
||||
- key: KIGI_GOAL
|
||||
value: "0"
|
||||
mock:
|
||||
response: "unused — this scenario never submits a prompt."
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
- action: focus_prompt
|
||||
- action: type_text
|
||||
text: "/goal"
|
||||
- action: wait
|
||||
millis: 300
|
||||
# Flag off ⇒ /goal is gated out, so its description never renders.
|
||||
- action: assert_not_contains
|
||||
text: autonomous goal
|
||||
- action: screenshot
|
||||
name: goal-absent-from-slash-menu-presession
|
||||
note: "/goal NOT advertised pre-session when the flag is off (fail-closed)."
|
||||
# Prove the pre-session menu is functional: an AlwaysOn builtin still shows.
|
||||
# Clear the typed `/goal` (5 chars) with explicit backspaces, then type /compact.
|
||||
- action: keys
|
||||
keys: "<BS><BS><BS><BS><BS>"
|
||||
- action: type_text
|
||||
text: "/compact"
|
||||
- action: wait
|
||||
millis: 300
|
||||
- action: assert_contains
|
||||
text: Compact conversation history
|
||||
- action: assert_running
|
||||
- action: screenshot
|
||||
name: always-on-builtin-in-slash-menu-presession
|
||||
note: "AlwaysOn /compact still advertised pre-session — the menu works; only /goal is gated."
|
||||
@@ -0,0 +1,40 @@
|
||||
name: html-entities
|
||||
description: >
|
||||
Regression: HTML character entities in assistant prose must
|
||||
render decoded (`<` -> `<`, `>` -> `>`, `&` -> `&`, `—` -> em
|
||||
dash) rather than as literal `<`/`>` text in the real pager.
|
||||
terminal:
|
||||
rows: 40
|
||||
cols: 110
|
||||
mock:
|
||||
response: "Result is vec<T> with a & b and an — dash."
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: type_text
|
||||
text: "show entities"
|
||||
- action: keys
|
||||
keys: "<Enter>"
|
||||
# The decoded angle brackets only appear once the entity transform runs.
|
||||
- action: wait_for_text
|
||||
text: "vec<T>"
|
||||
timeout_ms: 30000
|
||||
- action: assert_contains
|
||||
text: "a & b"
|
||||
- action: assert_contains
|
||||
text: "an — dash"
|
||||
# The raw entity source must not survive to the screen.
|
||||
- action: assert_not_contains
|
||||
text: "<"
|
||||
- action: assert_not_contains
|
||||
text: ">"
|
||||
- action: assert_not_contains
|
||||
text: "&"
|
||||
- action: assert_not_contains
|
||||
text: "—"
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
- action: screenshot
|
||||
name: html-entities
|
||||
note: Assistant prose with decoded HTML entities.
|
||||
@@ -0,0 +1,38 @@
|
||||
name: html-entities-rendering
|
||||
description: >
|
||||
Capture the real pager's rendering of HTML character entities in BOTH a
|
||||
prose paragraph and a markdown table cell. Used for a before/after
|
||||
comparison against the pre-fix binary (origin/main) and the with-fix
|
||||
binary; no `assert_not_contains` so the same scenario runs against both.
|
||||
terminal:
|
||||
rows: 40
|
||||
cols: 110
|
||||
mock:
|
||||
response: |
|
||||
Prose: vec<T> and a & b and an — dash.
|
||||
|
||||
| Kind | Sample |
|
||||
|------|--------|
|
||||
| code | vec<T> |
|
||||
| pair | a & b |
|
||||
| dash | an — dash |
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: type_text
|
||||
text: "show entities"
|
||||
- action: keys
|
||||
keys: "<Enter>"
|
||||
# Wait for the table to be fully rendered — the "dash" row is the last
|
||||
# one, and its content reaches the screen once streaming completes.
|
||||
- action: wait_for_text
|
||||
text: "dash"
|
||||
timeout_ms: 30000
|
||||
- action: wait
|
||||
millis: 500
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
- action: screenshot
|
||||
name: rendering
|
||||
note: Prose paragraph + markdown table with HTML entities.
|
||||
@@ -0,0 +1,49 @@
|
||||
name: image-chip-preview-path-free
|
||||
description: >
|
||||
Path-free image chips with post-paste preview (no-pixels terminal matrix cell).
|
||||
PTY harness has no TERM_PROGRAM brand, so overlay is metadata + path.
|
||||
Screenshots are retained for video stills.
|
||||
terminal:
|
||||
rows: 40
|
||||
cols: 120
|
||||
mock:
|
||||
response: "SCRIPTED_IMAGE_PREVIEW ok"
|
||||
fixture_images:
|
||||
- name: path_preview
|
||||
extension: png
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: paste_image_paths
|
||||
images: [path_preview]
|
||||
- action: assert_contains
|
||||
text: "Image #1"
|
||||
- action: assert_not_contains
|
||||
text: "Image #1:"
|
||||
- action: assert_contains
|
||||
text: "Format:"
|
||||
- action: assert_contains
|
||||
text: "path_preview.png"
|
||||
- action: screenshot
|
||||
name: image-chip-preview-after-paste
|
||||
note: Path-free Image #1 chip with metadata/path preview (no graphics protocol).
|
||||
- action: keys
|
||||
keys: "PREVIEW_DISMISSED_64280529"
|
||||
- action: assert_contains
|
||||
text: "PREVIEW_DISMISSED_64280529"
|
||||
- action: assert_not_contains
|
||||
text: "Format:"
|
||||
- action: assert_not_contains
|
||||
text: "Path:"
|
||||
- action: assert_not_contains
|
||||
text: "path_preview.png"
|
||||
- action: screenshot
|
||||
name: image-chip-after-type
|
||||
note: Typing after the chip dismisses the near-cursor preview.
|
||||
- action: keys
|
||||
keys: "<Esc>"
|
||||
- action: keys
|
||||
keys: "<Esc>"
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
@@ -0,0 +1,66 @@
|
||||
name: image-inputs
|
||||
description: "Validate all supported image input forms: path paste, file URL paste, escaped path paste, drag/drop path paste, and clipboard image paste."
|
||||
terminal:
|
||||
rows: 40
|
||||
cols: 120
|
||||
mock:
|
||||
response: "SCRIPTED_IMAGE_RESPONSE received image attachments."
|
||||
fixture_images:
|
||||
- name: path_image
|
||||
extension: png
|
||||
- name: file_url_image
|
||||
extension: png
|
||||
- name: escaped path image
|
||||
extension: png
|
||||
- name: dropped_image
|
||||
extension: png
|
||||
- name: clipboard_image
|
||||
extension: png
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: paste_image_paths
|
||||
images: [path_image]
|
||||
- action: assert_contains
|
||||
text: "Image #1"
|
||||
- action: paste_image_file_urls
|
||||
images: [file_url_image]
|
||||
- action: assert_contains
|
||||
text: "Image #2"
|
||||
- action: paste_escaped_image_paths
|
||||
images: ["escaped path image"]
|
||||
- action: assert_contains
|
||||
text: "Image #3"
|
||||
- action: drop_images_prompt
|
||||
images: [dropped_image]
|
||||
- action: assert_contains
|
||||
text: "Image #4"
|
||||
- action: paste_clipboard_image
|
||||
image: clipboard_image
|
||||
- action: assert_contains
|
||||
text: "Image #4"
|
||||
- action: keys
|
||||
keys: "<Right>"
|
||||
- action: screenshot
|
||||
name: image-inputs
|
||||
note: Prompt with five image chips from all supported input forms.
|
||||
- action: keys
|
||||
keys: "<Esc>"
|
||||
- action: drop_text_prompt
|
||||
text: " describe these images"
|
||||
- action: keys
|
||||
keys: "<Enter>"
|
||||
- action: wait_for_text
|
||||
text: SCRIPTED_IMAGE_RESPONSE
|
||||
timeout_ms: 30000
|
||||
- action: assert_chat_completion
|
||||
- action: assert_request_image_count
|
||||
min: 4
|
||||
- action: assert_inline_images
|
||||
min: 4
|
||||
mime_type: image/png
|
||||
width: { min: 8 }
|
||||
height: { min: 8 }
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
@@ -0,0 +1,44 @@
|
||||
name: image-normalize-corrupt-dropped
|
||||
description: >-
|
||||
Fix 2 + Fix 5: a CRC-corrupt PNG is rejected by the integrity check
|
||||
(no image bytes reach the model), and the user-turn system reminder
|
||||
surfaces an <image_dropped_notice> tag describing the drop.
|
||||
terminal:
|
||||
rows: 40
|
||||
cols: 120
|
||||
mock:
|
||||
response: "SCRIPTED_IMAGE_RESPONSE received image attachments."
|
||||
fixture_images:
|
||||
- name: corrupt_image
|
||||
extension: png
|
||||
kind: crc_corrupt_png
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: paste_image_paths
|
||||
images: [corrupt_image]
|
||||
- action: assert_contains
|
||||
text: "Image #1"
|
||||
- action: drop_text_prompt
|
||||
text: "Describe the image."
|
||||
- action: keys
|
||||
keys: "<Enter>"
|
||||
- action: wait_for_text
|
||||
text: SCRIPTED_IMAGE_RESPONSE
|
||||
timeout_ms: 30000
|
||||
- action: assert_chat_completion
|
||||
# Fix 2: integrity check failed → image dropped before send.
|
||||
- action: assert_request_image_count
|
||||
min: 0
|
||||
- action: assert_inline_images
|
||||
min: 0
|
||||
mime_type: image/png
|
||||
# Fix 5: <image_dropped_notice> system-reminder reaches the model.
|
||||
- action: assert_request_contains
|
||||
text: "<image_dropped_notice>"
|
||||
- action: screenshot
|
||||
name: corrupt-dropped
|
||||
note: Corrupt PNG dropped before send; drop notice forwarded to model.
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
@@ -0,0 +1,54 @@
|
||||
name: image-normalize-persist-atomic
|
||||
description: >-
|
||||
Fix 4: pasted images persist atomically into the session store and
|
||||
leave no `*.tmp` orphans behind, even when multiple images are pasted
|
||||
in quick succession.
|
||||
terminal:
|
||||
rows: 40
|
||||
cols: 120
|
||||
mock:
|
||||
response: "SCRIPTED_IMAGE_RESPONSE received image attachments."
|
||||
fixture_images:
|
||||
- name: img_a
|
||||
extension: png
|
||||
- name: img_b
|
||||
extension: png
|
||||
- name: img_c
|
||||
extension: png
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: paste_image_paths
|
||||
images: [img_a]
|
||||
- action: assert_contains
|
||||
text: "Image #1"
|
||||
- action: paste_image_paths
|
||||
images: [img_b]
|
||||
- action: assert_contains
|
||||
text: "Image #2"
|
||||
- action: paste_image_paths
|
||||
images: [img_c]
|
||||
- action: assert_contains
|
||||
text: "Image #3"
|
||||
- action: keys
|
||||
keys: "<Right>"
|
||||
- action: keys
|
||||
keys: "<Esc>"
|
||||
- action: drop_text_prompt
|
||||
text: " Describe the images."
|
||||
- action: keys
|
||||
keys: "<Enter>"
|
||||
- action: wait_for_text
|
||||
text: SCRIPTED_IMAGE_RESPONSE
|
||||
timeout_ms: 30000
|
||||
- action: assert_chat_completion
|
||||
- action: assert_request_image_count
|
||||
min: 3
|
||||
# Fix 4: persist_to_session is atomic — no leftover *.tmp files.
|
||||
- action: assert_no_temp_artifacts
|
||||
- action: screenshot
|
||||
name: persist-atomic
|
||||
note: Three images pasted and persisted with no .tmp orphans left behind.
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
@@ -0,0 +1,86 @@
|
||||
name: inline-edit-dismiss-returns-to-editor
|
||||
description: >-
|
||||
Esc from the inline resubmit popup returns to the still-open editor with
|
||||
the edit intact (proven live by typing another char), and a second Esc
|
||||
discards the edit entirely: the entry re-renders its original text and the
|
||||
transcript is untouched.
|
||||
terminal:
|
||||
rows: 40
|
||||
cols: 110
|
||||
mock:
|
||||
response: "DISMISS_MOCK_ANSWER from the scripted server."
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: type_text
|
||||
text: "polish the docs DISMISS_BASE"
|
||||
- action: keys
|
||||
keys: "<Enter>"
|
||||
- action: wait_for_text
|
||||
text: DISMISS_MOCK_ANSWER
|
||||
timeout_ms: 30000
|
||||
- action: wait
|
||||
millis: 500
|
||||
# Tab focuses the scrollback (raw byte: ptyctl's <Tab> notation is not
|
||||
# encodable by the Xterm key encoder and would be silently skipped).
|
||||
- action: type_text
|
||||
text: "\t"
|
||||
# Footer swaps to the scrollback hint set once the pane owns keys.
|
||||
- action: wait_for_text
|
||||
text: "Space:prompt"
|
||||
timeout_ms: 10000
|
||||
# Up-arrow clamps the selection onto the first selectable entry — the
|
||||
# user prompt (bare `k` needs vim mode; in the default mode a letter
|
||||
# bounces focus back to the composer and types itself).
|
||||
- action: keys
|
||||
keys: "<Up><Up><Up><Up><Up><Up>"
|
||||
- action: keys
|
||||
keys: "<Enter>"
|
||||
- action: type_text
|
||||
text: " DISMISS_EDIT"
|
||||
- action: wait_for_text
|
||||
text: "DISMISS_BASE DISMISS_EDIT"
|
||||
timeout_ms: 10000
|
||||
- action: keys
|
||||
keys: "<Enter>"
|
||||
- action: wait_for_text
|
||||
text: "Resubmit from here"
|
||||
timeout_ms: 15000
|
||||
- action: screenshot
|
||||
name: popup-over-editor
|
||||
note: Inline resubmit popup raised over the open editor.
|
||||
# Esc dismisses the popup and drops back into the editor, edit intact ...
|
||||
- action: keys
|
||||
keys: "<Esc>"
|
||||
- action: wait
|
||||
millis: 500
|
||||
- action: assert_not_contains
|
||||
text: "Resubmit from here"
|
||||
- action: assert_contains
|
||||
text: "DISMISS_BASE DISMISS_EDIT"
|
||||
# ... and the editor is still LIVE: another typed char lands in it.
|
||||
- action: keys
|
||||
keys: "X"
|
||||
- action: wait_for_text
|
||||
text: "DISMISS_EDITX"
|
||||
timeout_ms: 10000
|
||||
- action: keys
|
||||
keys: "<BS>"
|
||||
# Second Esc discards the edit: the entry re-renders the original text.
|
||||
- action: keys
|
||||
keys: "<Esc>"
|
||||
- action: wait
|
||||
millis: 500
|
||||
- action: assert_not_contains
|
||||
text: "DISMISS_EDIT"
|
||||
- action: assert_contains
|
||||
text: "DISMISS_BASE"
|
||||
- action: assert_contains
|
||||
text: DISMISS_MOCK_ANSWER
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
- action: assert_running
|
||||
- action: screenshot
|
||||
name: edit-discarded
|
||||
note: Edit discarded by Esc; original prompt text restored in place.
|
||||
@@ -0,0 +1,100 @@
|
||||
name: inline-edit-resubmit
|
||||
description: >-
|
||||
Inline edit-and-resubmit happy path: select a previous user prompt in the
|
||||
scrollback (Tab, k), Enter opens the in-place editor, append text, Enter
|
||||
raises the inline rewind popup ("Resubmit from here", files-only row
|
||||
hidden), `b` (conversation only) + `y` (last-prompt confirm) rewinds the
|
||||
conversation and resubmits the EDITED text as a fresh turn. Distinct
|
||||
per-turn mock sentinels prove the old tail was truncated and a new turn
|
||||
streamed; no "Reverted conversation" note is shown.
|
||||
terminal:
|
||||
rows: 40
|
||||
cols: 110
|
||||
mock:
|
||||
response: "FALLBACK_MOCK_RESPONSE"
|
||||
turns:
|
||||
- "FIRST_TURN_ANSWER from the mock server before any edit."
|
||||
- "SECOND_TURN_ANSWER streamed after the inline resubmit."
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: type_text
|
||||
text: "fix the bug INLINE_BASE"
|
||||
- action: keys
|
||||
keys: "<Enter>"
|
||||
- action: wait_for_text
|
||||
text: FIRST_TURN_ANSWER
|
||||
timeout_ms: 30000
|
||||
# Let the turn-end chrome (completion marker) settle before navigating.
|
||||
- action: wait
|
||||
millis: 500
|
||||
# Tab focuses the scrollback; k clamps the selection upward onto the
|
||||
# first selectable entry — the user prompt.
|
||||
# Tab focuses the scrollback (raw byte: ptyctl's <Tab> notation is not
|
||||
# encodable by the Xterm key encoder and would be silently skipped).
|
||||
- action: type_text
|
||||
text: "\t"
|
||||
# Footer swaps to the scrollback hint set once the pane owns keys.
|
||||
- action: wait_for_text
|
||||
text: "Space:prompt"
|
||||
timeout_ms: 10000
|
||||
# Up-arrow clamps the selection onto the first selectable entry — the
|
||||
# user prompt (bare `k` needs vim mode; in the default mode a letter
|
||||
# bounces focus back to the composer and types itself).
|
||||
- action: keys
|
||||
keys: "<Up><Up><Up><Up><Up><Up>"
|
||||
- action: keys
|
||||
keys: "<Enter>"
|
||||
# The in-place editor is live: typed text mutates the entry on screen.
|
||||
- action: type_text
|
||||
text: " EDITED_RESUBMIT"
|
||||
- action: wait_for_text
|
||||
text: "INLINE_BASE EDITED_RESUBMIT"
|
||||
timeout_ms: 10000
|
||||
- action: keys
|
||||
keys: "<Enter>"
|
||||
# Inline variant of the rewind mode-select: retitled, files-only hidden.
|
||||
- action: wait_for_text
|
||||
text: "Resubmit from here"
|
||||
timeout_ms: 15000
|
||||
- action: assert_not_contains
|
||||
text: "File changes only"
|
||||
- action: screenshot
|
||||
name: inline-mode-select
|
||||
note: Inline resubmit popup with a/b rows and no files-only option.
|
||||
# b = Conversation only; target is the first prompt, so the classic
|
||||
# last-prompt confirm popup follows.
|
||||
- action: keys
|
||||
keys: "b"
|
||||
- action: wait_for_text
|
||||
text: "Confirm rewind"
|
||||
timeout_ms: 15000
|
||||
- action: keys
|
||||
keys: "y"
|
||||
# The rewind lands and the EDITED prompt is resubmitted: the second
|
||||
# scripted turn streams its own sentinel.
|
||||
- action: wait_for_text
|
||||
text: SECOND_TURN_ANSWER
|
||||
timeout_ms: 30000
|
||||
# Old tail is gone (truncated at the edited prompt) ...
|
||||
- action: assert_not_contains
|
||||
text: FIRST_TURN_ANSWER
|
||||
# ... without the classic rewind's system note ...
|
||||
- action: assert_not_contains
|
||||
text: "Reverted conversation"
|
||||
# ... and the popup is gone while the edited text stands as the prompt.
|
||||
- action: assert_not_contains
|
||||
text: "Resubmit from here"
|
||||
- action: assert_contains
|
||||
text: "INLINE_BASE EDITED_RESUBMIT"
|
||||
# The resubmitted request carried the edited text to the model.
|
||||
- action: assert_request_contains
|
||||
text: EDITED_RESUBMIT
|
||||
- action: assert_chat_completion
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
- action: assert_running
|
||||
- action: screenshot
|
||||
name: resubmitted
|
||||
note: Transcript truncated at the prompt and regenerated from the edit.
|
||||
@@ -0,0 +1,77 @@
|
||||
name: inline-edit-unchanged-exit
|
||||
description: >-
|
||||
Enter on an inline prompt editor whose text is unchanged just closes the
|
||||
editor: no rewind popup, no resubmit, transcript untouched. The editor
|
||||
being live is proven by a typed char mutating the entry on screen (then
|
||||
backspaced away); the editor being closed afterwards is proven by a
|
||||
follow-up `j` NOT appearing in the entry text (it moves the scrollback
|
||||
selection instead).
|
||||
terminal:
|
||||
rows: 40
|
||||
cols: 110
|
||||
mock:
|
||||
response: "UNCHANGED_MOCK_ANSWER from the scripted server."
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: type_text
|
||||
text: "leave me alone UNCHANGED_BASE"
|
||||
- action: keys
|
||||
keys: "<Enter>"
|
||||
- action: wait_for_text
|
||||
text: UNCHANGED_MOCK_ANSWER
|
||||
timeout_ms: 30000
|
||||
- action: wait
|
||||
millis: 500
|
||||
# Tab focuses the scrollback (raw byte: ptyctl's <Tab> notation is not
|
||||
# encodable by the Xterm key encoder and would be silently skipped).
|
||||
- action: type_text
|
||||
text: "\t"
|
||||
# Footer swaps to the scrollback hint set once the pane owns keys.
|
||||
- action: wait_for_text
|
||||
text: "Space:prompt"
|
||||
timeout_ms: 10000
|
||||
# Up-arrow clamps the selection onto the first selectable entry — the
|
||||
# user prompt (bare `k` needs vim mode; in the default mode a letter
|
||||
# bounces focus back to the composer and types itself).
|
||||
- action: keys
|
||||
keys: "<Up><Up><Up><Up><Up><Up>"
|
||||
- action: keys
|
||||
keys: "<Enter>"
|
||||
# Editor is live: a typed char shows up inside the entry ...
|
||||
- action: keys
|
||||
keys: "Z"
|
||||
- action: wait_for_text
|
||||
text: "UNCHANGED_BASEZ"
|
||||
timeout_ms: 10000
|
||||
# ... then restore the original text so the submit is a no-op.
|
||||
- action: keys
|
||||
keys: "<BS>"
|
||||
- action: keys
|
||||
keys: "<Enter>"
|
||||
- action: wait
|
||||
millis: 800
|
||||
# No rewind popup of either flavor ever appears.
|
||||
- action: assert_not_contains
|
||||
text: "Resubmit from here"
|
||||
- action: assert_not_contains
|
||||
text: "What do you want to rewind"
|
||||
# Editor closed: j navigates the scrollback instead of inserting.
|
||||
- action: keys
|
||||
keys: "j"
|
||||
- action: wait
|
||||
millis: 300
|
||||
- action: assert_not_contains
|
||||
text: "UNCHANGED_BASEj"
|
||||
# Transcript intact.
|
||||
- action: assert_contains
|
||||
text: "UNCHANGED_BASE"
|
||||
- action: assert_contains
|
||||
text: UNCHANGED_MOCK_ANSWER
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
- action: assert_running
|
||||
- action: screenshot
|
||||
name: unchanged-exit
|
||||
note: Unchanged Enter closed the editor silently; transcript untouched.
|
||||
@@ -0,0 +1,41 @@
|
||||
name: inline-image-memory
|
||||
description: "Paste one PNG, hammer redraws, verify Kitty graphics use transmit-once + place (not a=T every frame)."
|
||||
terminal:
|
||||
rows: 40
|
||||
cols: 120
|
||||
mock:
|
||||
response: "unused — scenario does not submit"
|
||||
fixture_images:
|
||||
- name: memory_test_image
|
||||
extension: png
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: paste_clipboard_image
|
||||
image: memory_test_image
|
||||
- action: assert_contains
|
||||
text: "Image #1"
|
||||
# Move cursor to image chip to trigger overlay preview graphics.
|
||||
- action: keys
|
||||
keys: "<Left><Left><Left>"
|
||||
- action: wait_for_kitty_graphics
|
||||
min: 1
|
||||
timeout_ms: 10000
|
||||
# Hammer redraws — each should NOT re-upload the full image.
|
||||
- action: keys
|
||||
keys: "<Right><Left><Right><Left><Right><Left><Right><Left>"
|
||||
- action: keys
|
||||
keys: "hello world this is noise to force redraws"
|
||||
- action: keys
|
||||
keys: "<Backspace><Backspace><Backspace><Backspace><Backspace>"
|
||||
- action: keys
|
||||
keys: "<Right><Right><Right><Right>"
|
||||
- action: wait_for_kitty_graphics
|
||||
min: 1
|
||||
timeout_ms: 5000
|
||||
- action: assert_kitty_graphics
|
||||
min: 1
|
||||
- action: screenshot
|
||||
name: inline-image-memory
|
||||
note: Single pasted PNG after redraw hammering.
|
||||
@@ -0,0 +1,47 @@
|
||||
name: input-modalities
|
||||
description: Validate scripted clipboard paste, mouse click, double/triple click, scroll, mouse selection, and drop-style input events.
|
||||
terminal:
|
||||
rows: 36
|
||||
cols: 100
|
||||
mock:
|
||||
response: "SCRIPTED_INPUT_RESPONSE from pasted and dropped content."
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: paste_prompt
|
||||
text: "clipboard paste text"
|
||||
- action: assert_contains
|
||||
text: "clipboard paste text"
|
||||
- action: double_click_text
|
||||
text: "paste"
|
||||
- action: triple_click_text
|
||||
text: "paste"
|
||||
- action: select_text_range
|
||||
from_text: "clipboard"
|
||||
to_text: "text"
|
||||
- action: scroll_at
|
||||
target:
|
||||
kind: prompt
|
||||
direction: down
|
||||
count: 3
|
||||
- action: scroll_at
|
||||
target:
|
||||
kind: prompt
|
||||
direction: up
|
||||
count: 2
|
||||
- action: drop_text_prompt
|
||||
text: " dropped text"
|
||||
- action: assert_contains
|
||||
text: "dropped text"
|
||||
- action: screenshot
|
||||
name: input-modalities
|
||||
note: Prompt after click, bracketed paste, double/triple click, scroll, selection drag, and drop text.
|
||||
- action: keys
|
||||
keys: "<Enter>"
|
||||
- action: wait_for_text
|
||||
text: SCRIPTED_INPUT_RESPONSE
|
||||
timeout_ms: 30000
|
||||
- action: assert_chat_completion
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
@@ -0,0 +1,62 @@
|
||||
name: mermaid-affordances
|
||||
description: >
|
||||
A ```mermaid fence renders inline as Unicode box-drawing art (drawn by the
|
||||
markdown renderer), with a clickable affordance row beneath it led by a dim
|
||||
"◇ mermaid" label ([Open Image] [Copy Image Path] [Copy Source]). Rendering of
|
||||
the full-fidelity PNG is lazy (on click), so no diagram PNG is produced just by
|
||||
detection. The PNG is NEVER transmitted as an inline image, so the treatment is
|
||||
identical in every terminal and no Kitty graphics escape is emitted — asserted
|
||||
against the raw PTY output.
|
||||
terminal:
|
||||
rows: 40
|
||||
cols: 100
|
||||
mock:
|
||||
response: |
|
||||
Here is a flow:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A --> B
|
||||
B --> C
|
||||
```
|
||||
|
||||
Done rendering the flow.
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: type_text
|
||||
text: "draw me a diagram"
|
||||
- action: keys
|
||||
keys: "<Enter>"
|
||||
# Mermaid detection runs when the turn finishes, so the affordance row appears
|
||||
# only after the response is complete. Poll for one of its buttons rather than
|
||||
# asserting mid-stream.
|
||||
- action: wait_for_text
|
||||
text: "[Copy Source]"
|
||||
timeout_ms: 30000
|
||||
- action: assert_chat_completion
|
||||
# The fence is rendered inline as Unicode art by the markdown renderer: a TD
|
||||
# flowchart routes forward edges through "▼" arrowheads. Asserting the art (not
|
||||
# the source keyword) confirms the inline diagram replaced the raw source.
|
||||
- action: assert_contains
|
||||
text: "▼"
|
||||
# The full affordance row is plain text: [Open Image] [Copy Image Path] [Copy Source].
|
||||
- action: assert_contains
|
||||
text: "[Open Image]"
|
||||
# The affordance row is led by the dim "◇ mermaid" label.
|
||||
- action: assert_contains
|
||||
text: "◇ mermaid"
|
||||
# Rendering is LAZY: detection alone never dispatches a PNG render, so the row's
|
||||
# transient "rendering diagram…" hint is absent until an [Open Image]/[Copy
|
||||
# Image Path] click. This distinguishes lazy from eager without depending on the
|
||||
# OS clipboard/viewer.
|
||||
- action: assert_not_contains
|
||||
text: "rendering diagram"
|
||||
# The full-fidelity PNG is text + mouse hit-rects only, never an inline image.
|
||||
- action: assert_no_kitty_graphics
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
- action: screenshot
|
||||
name: mermaid-affordances
|
||||
note: Mermaid fence rendered inline as Unicode art with a clickable affordance row.
|
||||
@@ -0,0 +1,52 @@
|
||||
name: mid-text-skill-token-echo
|
||||
description: >
|
||||
A plain prompt referencing a skill mid-message
|
||||
("great /test-skill do it") keeps the composer's teal token highlight in the
|
||||
scrollback echo after submit. The workspace seeds a repo-local
|
||||
.grok/skills/test-skill/SKILL.md so the real shell advertises the skill; the
|
||||
screenshots capture the styled composer (pre-submit) and the styled echo
|
||||
(post-submit) — the styled JSON artifacts carry the per-run fg colors for
|
||||
visual verification and demo material.
|
||||
terminal:
|
||||
rows: 40
|
||||
cols: 100
|
||||
environment:
|
||||
args: ["--trust"]
|
||||
workspace:
|
||||
git_init: true
|
||||
files:
|
||||
".grok/skills/test-skill/SKILL.md": "---\nname: test-skill\ndescription: Test skill for echo styling.\n---\n\nDo the thing.\n"
|
||||
mock:
|
||||
response: "MIDTEXT_SKILL_ECHO_DONE assistant reply after the mid-text skill prompt."
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
- action: focus_prompt
|
||||
- action: type_text
|
||||
text: "great /test-skill do it"
|
||||
- action: wait_for_text
|
||||
text: "great /test-skill do it"
|
||||
timeout_ms: 10000
|
||||
# Give the shell time to advertise the skill so the composer highlight (and
|
||||
# the submit-time range capture) is in effect before Enter.
|
||||
- action: wait
|
||||
millis: 2000
|
||||
- action: screenshot
|
||||
name: composer-token-highlighted
|
||||
note: "Composer with the mid-text /test-skill token teal while typing."
|
||||
- action: keys
|
||||
keys: "<Enter>"
|
||||
- action: wait_for_text
|
||||
text: MIDTEXT_SKILL_ECHO_DONE
|
||||
timeout_ms: 30000
|
||||
- action: assert_contains
|
||||
text: "great /test-skill do it"
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
- action: assert_running
|
||||
- action: screenshot
|
||||
name: echo-token-highlighted
|
||||
note: "Scrollback echo keeps the /test-skill token teal after submit (compare styled JSON with the composer capture)."
|
||||
@@ -0,0 +1,60 @@
|
||||
name: minimal-flush-left
|
||||
description: >-
|
||||
Minimal mode is flush-left: the welcome card paints edge-to-edge, and the
|
||||
prompt, status row, committed user/agent blocks, and info bar share that left
|
||||
edge (no block_pad / accent gutter inset). Screenshots capture idle and
|
||||
post-response for before/after PR review.
|
||||
terminal:
|
||||
rows: 30
|
||||
cols: 100
|
||||
# Minimal's inline-viewport startup probe needs cursor-position replies;
|
||||
# without them --minimal silently downgrades to full-height inline.
|
||||
respond_to_queries: true
|
||||
environment:
|
||||
args: ["--minimal", "--no-leader"]
|
||||
mock:
|
||||
response: "FLUSH_LEFT_RESPONSE — short answer aligned with the welcome card."
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: "minimal · /help"
|
||||
timeout_ms: 20000
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
- action: screenshot
|
||||
name: idle
|
||||
note: >-
|
||||
Idle minimal: welcome card at col 0; status "minimal · /help", prompt ❯,
|
||||
and info bar should also start at column 0 (no 3-col left pad).
|
||||
- action: type_text
|
||||
text: "hello flush left"
|
||||
- action: keys
|
||||
keys: "<Enter>"
|
||||
- action: wait_for_text
|
||||
text: FLUSH_LEFT_RESPONSE
|
||||
timeout_ms: 30000
|
||||
- action: assert_chat_completion
|
||||
- action: wait_for_text
|
||||
text: "minimal · /help"
|
||||
timeout_ms: 20000
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
- action: screenshot
|
||||
name: after-response
|
||||
note: >-
|
||||
After commit: user prompt line, agent response, status, and input are all
|
||||
flush-left with the welcome card's outer border.
|
||||
- action: type_text
|
||||
text: "/tra"
|
||||
- action: wait_for_text
|
||||
text: "View the full conversation transcript"
|
||||
timeout_ms: 10000
|
||||
- action: screenshot
|
||||
name: slash-menu
|
||||
note: >-
|
||||
Slash dropdown: the selected row (❯ /transcript …) and divider start at
|
||||
column 0, flush with the prompt above; the dim nav footer sits at col 1
|
||||
like the /resume and /mcps panels.
|
||||
- action: keys
|
||||
keys: "<Esc>"
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
@@ -0,0 +1,45 @@
|
||||
name: minimal-transcript-hint
|
||||
description: >-
|
||||
Minimal (scrollback-native) mode advertises the full-transcript shortcut in
|
||||
the info row under the prompt ("ctrl+o transcript"), minimal's stand-in for
|
||||
the shortcuts bar it never renders. Verifies the hint is present at idle and
|
||||
survives a prompt/response round-trip.
|
||||
terminal:
|
||||
rows: 30
|
||||
cols: 100
|
||||
# Minimal's inline-viewport startup probe needs cursor-position replies;
|
||||
# without them --minimal silently downgrades to full-height inline.
|
||||
respond_to_queries: true
|
||||
environment:
|
||||
args: ["--minimal", "--no-leader"]
|
||||
mock:
|
||||
response: "MINIMAL_SCENARIO_RESPONSE from the mock server."
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: "minimal · /help"
|
||||
timeout_ms: 20000
|
||||
- action: assert_contains
|
||||
text: "ctrl+o transcript"
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
- action: screenshot
|
||||
name: idle
|
||||
note: Idle minimal mode; the info row under the prompt ends with the "ctrl+o transcript" hint.
|
||||
- action: type_text
|
||||
text: "hello minimal"
|
||||
- action: keys
|
||||
keys: "<Enter>"
|
||||
- action: wait_for_text
|
||||
text: MINIMAL_SCENARIO_RESPONSE
|
||||
timeout_ms: 30000
|
||||
- action: assert_chat_completion
|
||||
- action: wait_for_text
|
||||
text: "minimal · /help"
|
||||
timeout_ms: 20000
|
||||
- action: assert_contains
|
||||
text: "ctrl+o transcript"
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
- action: screenshot
|
||||
name: after-response
|
||||
note: After a mocked response commits to scrollback the hint is still in the info row.
|
||||
@@ -0,0 +1,40 @@
|
||||
name: minimal-transcript-hint-apple-terminal
|
||||
description: >-
|
||||
On Apple Terminal Ctrl+O doubles as the (only working) interject chord. At
|
||||
idle / with an empty composer an interject is a silent no-op, so minimal's
|
||||
transcript remap keeps the key — the "Ctrl+O appears dead on Mac" fix. The
|
||||
info row advertises "ctrl+o transcript" at idle, and pressing Ctrl+O acts on
|
||||
the transcript (empty conversation → the "nothing to show" system block).
|
||||
terminal:
|
||||
rows: 30
|
||||
cols: 100
|
||||
# Minimal's inline-viewport startup probe needs cursor-position replies;
|
||||
# without them --minimal silently downgrades to full-height inline.
|
||||
respond_to_queries: true
|
||||
environment:
|
||||
args: ["--minimal", "--no-leader"]
|
||||
env:
|
||||
# Detected as TerminalName::AppleTerminal → InterjectPrompt binds Ctrl+O.
|
||||
- { key: TERM_PROGRAM, value: Apple_Terminal }
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: "minimal · /help"
|
||||
timeout_ms: 20000
|
||||
- action: assert_contains
|
||||
text: "ctrl+o transcript"
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
- action: screenshot
|
||||
name: apple-idle
|
||||
note: Apple Terminal detection, idle — the info row still advertises ctrl+o transcript.
|
||||
- action: keys
|
||||
keys: "<C-o>"
|
||||
- action: wait_for_text
|
||||
text: "No conversation transcript to view yet"
|
||||
timeout_ms: 20000
|
||||
- action: assert_running
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
- action: screenshot
|
||||
name: apple-after-ctrl-o
|
||||
note: Ctrl+O at idle acts on the transcript instead of silently no-op'ing into interject.
|
||||
@@ -0,0 +1,24 @@
|
||||
name: mock-response
|
||||
description: Validate a real prompt submission renders mocked assistant output and captures the resulting UI.
|
||||
terminal:
|
||||
rows: 40
|
||||
cols: 110
|
||||
mock:
|
||||
response: "SCRIPTED_MOCK_RESPONSE rendered by the real pager from the local mock inference server."
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: type_text
|
||||
text: "hello scripted tui"
|
||||
- action: keys
|
||||
keys: "<Enter>"
|
||||
- action: wait_for_text
|
||||
text: SCRIPTED_MOCK_RESPONSE
|
||||
timeout_ms: 30000
|
||||
- action: assert_chat_completion
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
- action: screenshot
|
||||
name: response
|
||||
note: Prompt submission and mocked assistant response.
|
||||
@@ -0,0 +1,33 @@
|
||||
name: paste-chip-double-click
|
||||
description: >
|
||||
Double-clicking a `[Pasted: N lines]` chip in the prompt expands it back
|
||||
into plain editable text; a single click only places the cursor on it.
|
||||
terminal:
|
||||
rows: 36
|
||||
cols: 100
|
||||
mock:
|
||||
response: "UNUSED_PASTE_CHIP_DOUBLE_CLICK"
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
# A 4-line bracketed paste collapses into a chip.
|
||||
- action: paste_prompt
|
||||
text: "alpha\nbravo\ncharlie\ndelta"
|
||||
- action: wait_for_text
|
||||
text: "[Pasted: 4 lines]"
|
||||
timeout_ms: 10000
|
||||
- action: double_click_text
|
||||
text: "Pasted:"
|
||||
- action: wait
|
||||
millis: 300
|
||||
# Chip expanded: label gone, buffer text plain and editable.
|
||||
- action: assert_not_contains
|
||||
text: "Pasted:"
|
||||
- action: assert_contains
|
||||
text: "charlie"
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
- action: screenshot
|
||||
name: paste-chip-expanded
|
||||
note: Paste chip expanded to plain text after double-click.
|
||||
@@ -0,0 +1,35 @@
|
||||
name: paste-chip-repaste-expands
|
||||
description: >
|
||||
Pasting the same multiline content twice expands the `[Pasted: N lines]`
|
||||
chip back into plain text instead of inserting a duplicate chip.
|
||||
terminal:
|
||||
rows: 36
|
||||
cols: 100
|
||||
mock:
|
||||
response: "UNUSED_PASTE_CHIP_REPASTE"
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: paste_prompt
|
||||
text: "alpha\nbravo\ncharlie\ndelta"
|
||||
- action: wait_for_text
|
||||
text: "[Pasted: 4 lines]"
|
||||
timeout_ms: 10000
|
||||
- action: paste_prompt
|
||||
text: "alpha\nbravo\ncharlie\ndelta"
|
||||
- action: wait
|
||||
millis: 300
|
||||
# Chip expanded, not duplicated: label gone, plain text present once.
|
||||
- action: assert_not_contains
|
||||
text: "Pasted:"
|
||||
- action: assert_contains
|
||||
text: "charlie"
|
||||
# A duplicate insert would abut the copies as "deltaalpha".
|
||||
- action: assert_not_contains
|
||||
text: "deltaalpha"
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
- action: screenshot
|
||||
name: paste-chip-repaste-expanded
|
||||
note: Second identical paste expanded the chip instead of adding another.
|
||||
@@ -0,0 +1,45 @@
|
||||
name: path-space-hyperlink
|
||||
description: >
|
||||
Tutor report (bottlerocket): plain-text file paths with spaces only partially
|
||||
linkify — OSC 8 / click / underline stopped at the first space in
|
||||
`Demo App.app`. Stream the repro path through the mock agent and
|
||||
capture the rendered UI so CI / local PTY runs prove the full path stays one
|
||||
clickable region.
|
||||
terminal:
|
||||
rows: 40
|
||||
cols: 160
|
||||
# Native OSC 8 brand so the pager actually emits hyperlink sequences in the
|
||||
# PTY stream (default harness brand is Unknown and skips OSC 8).
|
||||
environment:
|
||||
env:
|
||||
- key: TERM_PROGRAM
|
||||
value: WezTerm
|
||||
mock:
|
||||
response: |
|
||||
PATHSPACE_SENTINEL open "/Users/alice/src/app/release/mac-arm64/Demo App.app"
|
||||
(or release/mac/ on Intel).
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: type_text
|
||||
text: "show path"
|
||||
- action: keys
|
||||
keys: "<Enter>"
|
||||
- action: wait_for_text
|
||||
text: PATHSPACE_SENTINEL
|
||||
timeout_ms: 30000
|
||||
- action: wait_for_text
|
||||
text: "App.app"
|
||||
timeout_ms: 10000
|
||||
- action: wait
|
||||
millis: 400
|
||||
- action: assert_contains
|
||||
text: "Demo App.app"
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
- action: screenshot
|
||||
name: spaced-path
|
||||
note: >
|
||||
Full macOS app-bundle path with a space must render as one link region
|
||||
(not truncated at Demo).
|
||||
@@ -0,0 +1,30 @@
|
||||
name: plan-nudge-opt-out-no-show
|
||||
description: >
|
||||
Explicit opt-out: contextual hints now ship default-ON, so this scenario pins
|
||||
the master env `KIGI_CONTEXTUAL_HINTS=0` (global kill switch) before typing a
|
||||
planning keyword — the "Planning? Check out plan mode via shift+tab" banner
|
||||
must NOT appear. Mirrors plan_nudge_shows (which pins `=1`), exercising the
|
||||
disabled path explicitly.
|
||||
terminal:
|
||||
rows: 32
|
||||
cols: 100
|
||||
environment:
|
||||
env:
|
||||
- key: KIGI_CONTEXTUAL_HINTS
|
||||
value: "0"
|
||||
mock:
|
||||
response: "unused"
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: focus_prompt
|
||||
- action: type_text
|
||||
text: "lets plan the refactor"
|
||||
- action: wait
|
||||
millis: 700
|
||||
- action: assert_not_contains
|
||||
text: "plan mode via"
|
||||
- action: screenshot
|
||||
name: plan-nudge-opt-out
|
||||
note: No plan-nudge banner when the contextual-hints flag is explicitly disabled.
|
||||
@@ -0,0 +1,44 @@
|
||||
name: plan-nudge-shows
|
||||
description: >
|
||||
Plan-nudge end-to-end with the contextual-hints master pinned on
|
||||
(`KIGI_CONTEXTUAL_HINTS=1` for deterministic env control; the feature now ships
|
||||
default-ON): typing a planning keyword into the prompt shows the "Planning?
|
||||
Check out plan mode via shift+tab" banner. The seen cap is in-memory only, so a
|
||||
fresh run starts uncapped; the banner clears on its own ~3s TTL.
|
||||
terminal:
|
||||
rows: 32
|
||||
cols: 100
|
||||
environment:
|
||||
env:
|
||||
- key: KIGI_CONTEXTUAL_HINTS
|
||||
value: "1"
|
||||
mock:
|
||||
response: "unused"
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: focus_prompt
|
||||
# Promote the welcome prompt to a real session with a keyword-free prefix
|
||||
# (the agent view is where the plan-nudge routing lives), and let the session
|
||||
# settle to idle — the nudge is gated on `session.state.is_idle()`, so the
|
||||
# keyword must cross in after the freshly-created session is ready.
|
||||
- action: type_text
|
||||
text: "lets "
|
||||
- action: wait_for_text
|
||||
text: "test-model"
|
||||
timeout_ms: 10000
|
||||
- action: wait
|
||||
millis: 1500
|
||||
# Cross into the "plan" keyword now that the session is idle → rising-edge
|
||||
# nudge fires once.
|
||||
- action: type_text
|
||||
text: "plan the refactor"
|
||||
- action: wait_for_text
|
||||
text: "plan mode via"
|
||||
timeout_ms: 10000
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
- action: screenshot
|
||||
name: plan-nudge-shown
|
||||
note: Plan-nudge banner after typing a planning keyword.
|
||||
@@ -0,0 +1,52 @@
|
||||
name: release-notes-scroll
|
||||
description: >
|
||||
Open /release-notes (DocViewer modal) and verify keyboard + mouse-wheel scrolling
|
||||
moves the changelog body. Seeds CHANGELOG cache offline via setup is not available
|
||||
in YAML, so this scenario relies on CDN/cache if present and still asserts the
|
||||
modal chrome + scroll affordances. Prefer the pty_e2e Rust tests for deterministic
|
||||
offline seeding; this YAML exercises the scripted ptyctl runner path.
|
||||
terminal:
|
||||
rows: 40
|
||||
cols: 110
|
||||
mock:
|
||||
response: "SCRIPTED_RELEASE_NOTES_SCROLL unused for this scenario."
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
# Promote welcome → session and open release notes (uses CDN or disk cache).
|
||||
- action: type_text
|
||||
text: "/release-notes"
|
||||
- action: keys
|
||||
keys: "<Enter>"
|
||||
- action: wait_for_text
|
||||
text: Release Notes
|
||||
timeout_ms: 20000
|
||||
- action: assert_contains
|
||||
text: scroll
|
||||
# Keyboard scroll through the modal body.
|
||||
- action: keys
|
||||
keys: "<Down><Down><Down><Down><Down><Down><Down><Down><Down><Down>"
|
||||
- action: wait
|
||||
millis: 200
|
||||
- action: keys
|
||||
keys: "jjjjjjjjjj"
|
||||
- action: wait
|
||||
millis: 200
|
||||
# Mouse wheel at the center of the modal.
|
||||
- action: scroll
|
||||
row: 20
|
||||
col: 55
|
||||
direction: down
|
||||
count: 15
|
||||
- action: wait
|
||||
millis: 250
|
||||
- action: assert_contains
|
||||
text: Release Notes
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
- action: keys
|
||||
keys: "<Esc>"
|
||||
- action: screenshot
|
||||
name: release-notes-after-scroll
|
||||
note: Release notes modal after keyboard + wheel scroll.
|
||||
@@ -0,0 +1,97 @@
|
||||
name: shortcuts-help-detail
|
||||
description: >
|
||||
End-to-end cheatsheet flow exercising the full shortcuts-help stack: open the
|
||||
Keyboard Shortcuts modal from a live session, inline-expand a hint with the
|
||||
right arrow and collapse it with the left arrow (directional collapse),
|
||||
open the man-style detail screen with Enter (pattern B) showing the action's
|
||||
long_help, return to the browse list with Esc (not close), then dismiss the
|
||||
modal with the global Ctrl+X close chord.
|
||||
|
||||
Open/close uses Ctrl+X rather than Ctrl+. : both are bound to ShortcutsHelp
|
||||
(When::AgentScreen) and both are the in-modal global-close chord, but Ctrl+X
|
||||
has a legacy byte (0x18) the PTY reliably delivers whereas Ctrl+. has no
|
||||
Xterm-encodable byte. The prompt ignores Ctrl+X, so it bubbles to the
|
||||
AgentScreen binding.
|
||||
terminal:
|
||||
rows: 40
|
||||
cols: 110
|
||||
mock:
|
||||
response: "MOCK_SHORTCUTS_E2E assistant reply for the cheatsheet scenario."
|
||||
steps:
|
||||
# Reach a session so ShortcutsHelp (When::AgentScreen) is reachable.
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: type_text
|
||||
text: "hello cheatsheet"
|
||||
- action: keys
|
||||
keys: "<Enter>"
|
||||
- action: wait_for_text
|
||||
text: MOCK_SHORTCUTS_E2E
|
||||
timeout_ms: 30000
|
||||
# Open the cheatsheet via the reliably-encodable Ctrl+X (== Ctrl+. binding).
|
||||
- action: keys
|
||||
keys: "<C-x>"
|
||||
- action: wait_for_text
|
||||
text: Keyboard Shortcuts
|
||||
timeout_ms: 15000
|
||||
# Browse-mode chrome + shipped affordances (expand / collapse / detail hints).
|
||||
- action: assert_contains
|
||||
text: Essentials
|
||||
- action: assert_contains
|
||||
text: Enter details
|
||||
- action: assert_contains
|
||||
text: collapse
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
# Move selection off the section header onto the CancelTurn hint
|
||||
# (Essentials order: send, prompt, scrollback, cancel).
|
||||
- action: keys
|
||||
keys: "<Down><Down><Down>"
|
||||
- action: wait
|
||||
millis: 200
|
||||
# Inline-expand (right arrow): the action's long_help renders under the row.
|
||||
- action: keys
|
||||
keys: "<Right>"
|
||||
- action: wait_for_text
|
||||
text: Interrupts the
|
||||
timeout_ms: 15000
|
||||
# Collapse the same row with the left arrow (directional left-collapse).
|
||||
- action: keys
|
||||
keys: "<Left>"
|
||||
- action: wait
|
||||
millis: 200
|
||||
- action: assert_not_contains
|
||||
text: Interrupts the
|
||||
# Enter opens the in-modal man-style detail screen (pattern B).
|
||||
- action: keys
|
||||
keys: "<Enter>"
|
||||
- action: wait_for_text
|
||||
text: Esc back
|
||||
timeout_ms: 15000
|
||||
- action: assert_contains
|
||||
text: Interrupts the
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
# Esc returns to the browse list (must NOT close the modal).
|
||||
- action: keys
|
||||
keys: "<Esc>"
|
||||
- action: wait_for_text
|
||||
text: Enter details
|
||||
timeout_ms: 15000
|
||||
- action: assert_contains
|
||||
text: Keyboard Shortcuts
|
||||
# Global close: Ctrl+X dismisses the modal and restores the session.
|
||||
- action: keys
|
||||
keys: "<C-x>"
|
||||
- action: wait
|
||||
millis: 300
|
||||
- action: assert_not_contains
|
||||
text: Keyboard Shortcuts
|
||||
- action: assert_contains
|
||||
text: MOCK_SHORTCUTS_E2E
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
- action: screenshot
|
||||
name: shortcuts-help-detail-closed
|
||||
note: Cheatsheet inline-expand / detail / collapse flow complete; modal dismissed via Ctrl+X.
|
||||
@@ -0,0 +1,57 @@
|
||||
name: slash-resize-storm
|
||||
description: >
|
||||
Regression — resizing while the slash-command dropdown is open must not crash
|
||||
the pager (ratatui "index outside of buffer" on item row paint).
|
||||
terminal:
|
||||
rows: 40
|
||||
cols: 100
|
||||
mock:
|
||||
response: "SCRIPTED_SLASH_RESIZE_RESPONSE"
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: focus_prompt
|
||||
# Open the slash command menu (full list of matches).
|
||||
- action: type_text
|
||||
text: "/"
|
||||
- action: wait
|
||||
millis: 200
|
||||
- action: assert_running
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
# Height shrink while dropdown is painted — the historical failure mode.
|
||||
- action: resize
|
||||
rows: 12
|
||||
cols: 100
|
||||
- action: wait
|
||||
millis: 150
|
||||
- action: assert_running
|
||||
- action: resize
|
||||
rows: 8
|
||||
cols: 80
|
||||
- action: wait
|
||||
millis: 150
|
||||
- action: assert_running
|
||||
# Oscillate a few times like a dragged IDE panel.
|
||||
- action: resize
|
||||
rows: 28
|
||||
cols: 120
|
||||
- action: wait
|
||||
millis: 80
|
||||
- action: resize
|
||||
rows: 10
|
||||
cols: 60
|
||||
- action: wait
|
||||
millis: 80
|
||||
- action: resize
|
||||
rows: 6
|
||||
cols: 40
|
||||
- action: wait
|
||||
millis: 150
|
||||
- action: assert_running
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
- action: screenshot
|
||||
name: slash-after-resize-storm
|
||||
note: Pager still alive after slash dropdown + aggressive height shrinks.
|
||||
@@ -0,0 +1,59 @@
|
||||
name: small-screen-tip-band
|
||||
description: >
|
||||
Small-screen tip happy path with the contextual-hints master pinned on: a 24
|
||||
row terminal sits in the 21..=28 band (above the 20-row auto-compact
|
||||
threshold), so the first stable agent-view draw shows the one-shot "Tight on
|
||||
space? Try /compact-mode" banner. It expires on its ~3s TTL and — because the
|
||||
trigger is a per-run one-shot — never re-shows, even after dipping into the
|
||||
auto-compact zone and resizing back into the band.
|
||||
terminal:
|
||||
rows: 24
|
||||
cols: 100
|
||||
environment:
|
||||
env:
|
||||
- key: KIGI_CONTEXTUAL_HINTS
|
||||
value: "1"
|
||||
mock:
|
||||
response: "unused"
|
||||
steps:
|
||||
# The prompt marker renders at every height; the first char promotes the
|
||||
# welcome prompt to a real session (the agent view, where the tip fires).
|
||||
- action: wait_for_text
|
||||
text: "❯"
|
||||
timeout_ms: 20000
|
||||
- action: focus_prompt
|
||||
- action: type_text
|
||||
text: "hi"
|
||||
- action: wait_for_text
|
||||
text: "Tight on space? Try /compact-mode"
|
||||
timeout_ms: 15000
|
||||
- action: screenshot
|
||||
name: small-screen-tip-shown
|
||||
note: One-shot /compact-mode tip on a 24-row terminal.
|
||||
# Past the ~3s TTL the banner clears on its own.
|
||||
- action: wait
|
||||
millis: 4500
|
||||
- action: assert_not_contains
|
||||
text: "Tight on space"
|
||||
# Dip into the auto-compact zone (19 <= 20) and back into the band: the
|
||||
# one-shot must not re-trigger.
|
||||
- action: resize
|
||||
rows: 19
|
||||
cols: 100
|
||||
- action: wait
|
||||
millis: 400
|
||||
- action: resize
|
||||
rows: 24
|
||||
cols: 100
|
||||
- action: wait
|
||||
millis: 400
|
||||
- action: type_text
|
||||
text: "x"
|
||||
- action: wait
|
||||
millis: 600
|
||||
- action: assert_not_contains
|
||||
text: "Tight on space"
|
||||
- action: assert_running
|
||||
- action: screenshot
|
||||
name: small-screen-tip-no-reshow
|
||||
note: No re-show after resizes — the trigger is one-shot per run.
|
||||
@@ -0,0 +1,35 @@
|
||||
name: small-screen-tip-no-show-tall
|
||||
description: >
|
||||
No-show edge above the band: a 40-row terminal is roomy, so the first stable
|
||||
agent-view draw consumes the one-shot evaluation without showing the
|
||||
"Tight on space? Try /compact-mode" banner — even with the contextual-hints
|
||||
master pinned on.
|
||||
terminal:
|
||||
rows: 40
|
||||
cols: 100
|
||||
environment:
|
||||
env:
|
||||
- key: KIGI_CONTEXTUAL_HINTS
|
||||
value: "1"
|
||||
mock:
|
||||
response: "unused"
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: focus_prompt
|
||||
# Promote the welcome prompt to a real session; the status bar's model name
|
||||
# proves the agent view has drawn (the trigger's evaluation point).
|
||||
- action: type_text
|
||||
text: "hi"
|
||||
- action: wait_for_text
|
||||
text: "test-model"
|
||||
timeout_ms: 15000
|
||||
- action: wait
|
||||
millis: 1200
|
||||
- action: assert_not_contains
|
||||
text: "Tight on space"
|
||||
- action: assert_running
|
||||
- action: screenshot
|
||||
name: small-screen-tip-absent-tall
|
||||
note: No /compact-mode tip on a 40-row terminal.
|
||||
@@ -0,0 +1,36 @@
|
||||
name: small-screen-tip-no-show-tiny
|
||||
description: >
|
||||
No-show edge below the band: at 14 rows auto-compact is active instead (and
|
||||
the banner row cannot render at or below 16 rows), so the "Tight on space?
|
||||
Try /compact-mode" tip never appears — even with the contextual-hints master
|
||||
pinned on.
|
||||
terminal:
|
||||
rows: 14
|
||||
cols: 100
|
||||
environment:
|
||||
env:
|
||||
- key: KIGI_CONTEXTUAL_HINTS
|
||||
value: "1"
|
||||
mock:
|
||||
response: "unused"
|
||||
steps:
|
||||
# The welcome menu may be trimmed at 14 rows; the prompt marker always paints.
|
||||
- action: wait_for_text
|
||||
text: "❯"
|
||||
timeout_ms: 20000
|
||||
- action: focus_prompt
|
||||
# Promote the welcome prompt to a real session; the status bar's model name
|
||||
# proves the agent view has drawn (the trigger's evaluation point).
|
||||
- action: type_text
|
||||
text: "hi"
|
||||
- action: wait_for_text
|
||||
text: "test-model"
|
||||
timeout_ms: 15000
|
||||
- action: wait
|
||||
millis: 1200
|
||||
- action: assert_not_contains
|
||||
text: "Tight on space"
|
||||
- action: assert_running
|
||||
- action: screenshot
|
||||
name: small-screen-tip-absent-tiny
|
||||
note: No /compact-mode tip at 14 rows (auto-compact active instead).
|
||||
@@ -0,0 +1,119 @@
|
||||
name: table-cell-selection
|
||||
description: Validate table-aware selection — a drag inside a rendered table selects by cell (within-cell text, cross-cell TSV) instead of sweeping whole lines across columns.
|
||||
terminal:
|
||||
rows: 40
|
||||
cols: 100
|
||||
environment:
|
||||
env:
|
||||
# Force the remote clipboard route so copies surface as OSC 52 (readback).
|
||||
- key: SSH_CONNECTION
|
||||
value: "scripted-test 1 127.0.0.1 2"
|
||||
# word_select persists highlights (no 150ms flash race) and enables the
|
||||
# multi-click ladder the whole-table triple-click rides on.
|
||||
config_toml: |
|
||||
[ui]
|
||||
keep_text_selection = "word_select"
|
||||
mock:
|
||||
response: |
|
||||
Here is the roster:
|
||||
|
||||
| Animal | Sound |
|
||||
| --- | --- |
|
||||
| ZebraOne | `NeighNoise` |
|
||||
| BravoCat | MeowNoise |
|
||||
| Quick Fox | IgnoreMe |
|
||||
|
||||
Done.
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: type_text
|
||||
text: "show the roster table"
|
||||
- action: keys
|
||||
keys: "<Enter>"
|
||||
- action: wait_for_text
|
||||
text: BravoCat
|
||||
timeout_ms: 30000
|
||||
# Column drag: copies that column only — no other column, no │ glyphs.
|
||||
- action: select_text_range
|
||||
from_text: "ZebraOne"
|
||||
to_text: "BravoCat"
|
||||
- action: assert_osc52_contains
|
||||
text: "ZebraOne\nBravoCat"
|
||||
- action: assert_osc52_not_contains
|
||||
text: "│"
|
||||
- action: assert_osc52_not_contains
|
||||
text: "NeighNoise"
|
||||
# Highlight matches the copy: cell content only.
|
||||
- action: assert_highlight_run
|
||||
at_text: "ZebraOne"
|
||||
equals: "ZebraOne"
|
||||
- action: assert_highlight_run
|
||||
at_text: "BravoCat"
|
||||
equals: "BravoCat"
|
||||
- action: assert_text_not_highlighted
|
||||
text: "NeighNoise"
|
||||
# Dead-zone: overshooting onto the border + neighbor padding must not
|
||||
# escalate — copy and highlight stay the single cell.
|
||||
- action: select_text_range
|
||||
from_text: "ZebraOne"
|
||||
to_text: "ZebraOne"
|
||||
to_offset_cols: 3
|
||||
- action: assert_highlight_run
|
||||
at_text: "ZebraOne"
|
||||
equals: "ZebraOne"
|
||||
- action: assert_text_not_highlighted
|
||||
text: "NeighNoise"
|
||||
- action: assert_osc52_not_contains
|
||||
text: "NeighNoise"
|
||||
# Rectangle across both columns: TSV (tab-separated cells, newline rows).
|
||||
# NeighNoise is an inline-code chip: it must join the uniform highlight
|
||||
# band (assert_highlight_run checks background uniformity).
|
||||
- action: select_text_range
|
||||
from_text: "ZebraOne"
|
||||
to_text: "MeowNoise"
|
||||
- action: assert_osc52_contains
|
||||
text: "ZebraOne\tNeighNoise\nBravoCat\tMeowNoise"
|
||||
- action: assert_highlight_run
|
||||
at_text: "NeighNoise"
|
||||
equals: "NeighNoise"
|
||||
# Within-cell drag stays clamped to the cell.
|
||||
- action: select_text_range
|
||||
from_text: "Quick"
|
||||
to_text: "Fox"
|
||||
- action: assert_osc52_contains
|
||||
text: "Quick Fox"
|
||||
- action: assert_osc52_not_contains
|
||||
text: "IgnoreMe"
|
||||
# Interior spaces stay highlighted, edge padding does not.
|
||||
- action: assert_highlight_run
|
||||
at_text: "Quick"
|
||||
equals: "Quick Fox"
|
||||
- action: assert_text_not_highlighted
|
||||
text: "IgnoreMe"
|
||||
# Grid-line-anchored drag stays linear: copy carries border glyphs
|
||||
# (impossible for any TSV copy).
|
||||
- action: select_text_range
|
||||
from_text: "├"
|
||||
to_text: "BravoCat"
|
||||
- action: assert_osc52_contains
|
||||
text: "┼"
|
||||
- action: assert_osc52_contains
|
||||
text: "│ ZebraOne"
|
||||
# Triple-click on a grid line is the whole-table TSV shortcut.
|
||||
- action: triple_click_text
|
||||
text: "├"
|
||||
- action: assert_osc52_contains
|
||||
text: "Animal\tSound\nZebraOne\tNeighNoise\nBravoCat\tMeowNoise\nQuick Fox\tIgnoreMe"
|
||||
- action: assert_highlight_run
|
||||
at_text: "Animal"
|
||||
equals: "Animal"
|
||||
- action: assert_highlight_run
|
||||
at_text: "IgnoreMe"
|
||||
equals: "IgnoreMe"
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
- action: screenshot
|
||||
name: table-grid-selection
|
||||
note: Cell-aware selection in a rendered markdown table.
|
||||
@@ -0,0 +1,51 @@
|
||||
name: tool-header-path-selection
|
||||
description: >-
|
||||
Drag-select on a tool command header copies only the meaningful operand
|
||||
(command / path), not the label prefix. Uses a user `!` bash execute block
|
||||
because the label style is `Run {cmd}` and the command is the selectable span
|
||||
— same Selectable::Spans mechanism as Read {path}.
|
||||
terminal:
|
||||
rows: 45
|
||||
cols: 110
|
||||
environment:
|
||||
env:
|
||||
- key: SSH_CONNECTION
|
||||
value: "scripted-test 1 127.0.0.1 2"
|
||||
mock:
|
||||
response: "BASH_SESSION_READY"
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 25000
|
||||
- action: type_text
|
||||
text: "go"
|
||||
- action: keys
|
||||
keys: "<Enter>"
|
||||
- action: wait_for_text
|
||||
text: BASH_SESSION_READY
|
||||
timeout_ms: 45000
|
||||
# Unique token so we can locate the command span unambiguously.
|
||||
- action: paste_prompt
|
||||
text: "! printf 'TOOL_HDR_SEL_TOKEN\\n'"
|
||||
- action: keys
|
||||
keys: "<Enter>"
|
||||
- action: wait_for_text
|
||||
text: "TOOL_HDR_SEL_TOKEN"
|
||||
timeout_ms: 30000
|
||||
- action: wait
|
||||
millis: 400
|
||||
# Select only the command operand (not the "Run " / "$ " label prefix).
|
||||
- action: select_text_range
|
||||
from_text: "printf"
|
||||
to_text: "TOOL_HDR_SEL_TOKEN"
|
||||
- action: assert_osc52_contains
|
||||
text: "TOOL_HDR_SEL_TOKEN"
|
||||
# Label prefix must not appear alone as the copied selection payload.
|
||||
# (Full line copy would include "Run " or "$ " before the command.)
|
||||
- action: assert_osc52_not_contains
|
||||
text: "Run printf"
|
||||
- action: assert_osc52_not_contains
|
||||
text: "$ printf"
|
||||
- action: screenshot
|
||||
name: tool-header-operand-selected
|
||||
note: Tool header selection copies the command operand, not the Run/$ label.
|
||||
@@ -0,0 +1,41 @@
|
||||
name: undo-tip-clear-shows
|
||||
description: >
|
||||
Undo tip end-to-end with the contextual-hints master pinned on
|
||||
(`KIGI_CONTEXTUAL_HINTS=1` for deterministic env control; the feature now ships
|
||||
default-ON): wiping a substantial prompt draft with Ctrl+U shows the "… to
|
||||
undo" banner. The seen cap is in-memory only, so a fresh run starts uncapped;
|
||||
the banner clears on its own ~3s TTL (no early retire on undo).
|
||||
terminal:
|
||||
rows: 32
|
||||
cols: 100
|
||||
environment:
|
||||
env:
|
||||
- key: KIGI_CONTEXTUAL_HINTS
|
||||
value: "1"
|
||||
mock:
|
||||
response: "unused"
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: focus_prompt
|
||||
# The first printable char promotes the welcome prompt to a real session
|
||||
# (the agent view, where the undo-tip routing lives); the rest accumulate
|
||||
# into that draft. 25 chars clears the detector's FIRE_PEAK_LEN (20).
|
||||
- action: type_text
|
||||
text: "aaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
- action: wait
|
||||
millis: 400
|
||||
- action: assert_contains
|
||||
text: "aaaaaaaaaa"
|
||||
# Ctrl+U kills the whole line: a substantial, recoverable wipe → tip shows.
|
||||
- action: keys
|
||||
keys: "<C-u>"
|
||||
- action: wait_for_text
|
||||
text: "to undo"
|
||||
timeout_ms: 10000
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
- action: screenshot
|
||||
name: undo-tip-shown
|
||||
note: Undo tip banner after a Ctrl+U wipe of a substantial draft.
|
||||
@@ -0,0 +1,42 @@
|
||||
name: undo-tip-completion-accept-no-show
|
||||
description: >
|
||||
No-show edge: accepting an @-file completion is not a wipe and must never
|
||||
show the undo tip, even though the accept replaces the typed @query with a
|
||||
file ref. (The precise residue-band regression — a long query collapsing to
|
||||
a tiny chip — is unit-tested in prompt_widget via FileSearchState test state;
|
||||
the real fuzzy matcher cannot match a >=20-char query to a tiny filename, so
|
||||
this scenario covers the end-to-end "accept never hints" behavior.)
|
||||
terminal:
|
||||
rows: 32
|
||||
cols: 100
|
||||
environment:
|
||||
env:
|
||||
- key: KIGI_CONTEXTUAL_HINTS
|
||||
value: "1"
|
||||
mock:
|
||||
response: "unused"
|
||||
workspace:
|
||||
files:
|
||||
"undo_tip_completion_target.md": "fixture file for @-completion accept"
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: focus_prompt
|
||||
# Type an @-query that matches the seeded file; wait for the dropdown.
|
||||
- action: type_text
|
||||
text: "@undo_tip_completion_targ"
|
||||
- action: wait_for_text
|
||||
text: "undo_tip_completion_target.md"
|
||||
timeout_ms: 10000
|
||||
# Accept with Tab — a completion, not a wipe.
|
||||
- action: keys
|
||||
keys: "<Tab>"
|
||||
- action: wait
|
||||
millis: 600
|
||||
- action: assert_not_contains
|
||||
text: "to undo"
|
||||
- action: assert_running
|
||||
- action: screenshot
|
||||
name: undo-tip-completion-accept
|
||||
note: Accepting an @-file completion shows no undo tip.
|
||||
@@ -0,0 +1,39 @@
|
||||
name: undo-tip-ctrl-c-clear-shows
|
||||
description: >
|
||||
Ctrl+C variant of the undo tip with the contextual-hints master pinned on
|
||||
(`KIGI_CONTEXTUAL_HINTS=1` for deterministic env control; the feature now ships
|
||||
default-ON): with a substantial draft present, Ctrl+C clears the textarea (the
|
||||
cancel-with-draft fall-through) — a recoverable wipe that shows the "… to undo"
|
||||
banner.
|
||||
terminal:
|
||||
rows: 32
|
||||
cols: 100
|
||||
environment:
|
||||
env:
|
||||
- key: KIGI_CONTEXTUAL_HINTS
|
||||
value: "1"
|
||||
mock:
|
||||
response: "unused"
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: focus_prompt
|
||||
# Type first so Ctrl+C lands in the agent view with a non-empty draft
|
||||
# (clear), not on the welcome screen (where bare Ctrl+C would quit).
|
||||
- action: type_text
|
||||
text: "bbbbbbbbbbbbbbbbbbbbbbbbb"
|
||||
- action: wait
|
||||
millis: 400
|
||||
- action: assert_contains
|
||||
text: "bbbbbbbbbb"
|
||||
- action: keys
|
||||
keys: "<C-c>"
|
||||
- action: wait_for_text
|
||||
text: "to undo"
|
||||
timeout_ms: 10000
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
- action: screenshot
|
||||
name: undo-tip-ctrl-c-shown
|
||||
note: Undo tip after a Ctrl+C clear of a substantial draft.
|
||||
@@ -0,0 +1,34 @@
|
||||
name: undo-tip-short-draft-no-show
|
||||
description: >
|
||||
No-show edge: wiping a SHORT draft (below the detector's FIRE_PEAK_LEN of 20
|
||||
chars) must NOT show the undo tip — only substantial wipes are worth hinting.
|
||||
terminal:
|
||||
rows: 32
|
||||
cols: 100
|
||||
environment:
|
||||
env:
|
||||
- key: KIGI_CONTEXTUAL_HINTS
|
||||
value: "1"
|
||||
mock:
|
||||
response: "unused"
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: focus_prompt
|
||||
# 10 chars — below the 20-char peak threshold.
|
||||
- action: type_text
|
||||
text: "cccccccccc"
|
||||
- action: wait
|
||||
millis: 400
|
||||
- action: assert_contains
|
||||
text: "cccccccccc"
|
||||
- action: keys
|
||||
keys: "<C-u>"
|
||||
- action: wait
|
||||
millis: 700
|
||||
- action: assert_not_contains
|
||||
text: "to undo"
|
||||
- action: screenshot
|
||||
name: undo-tip-short-draft
|
||||
note: No tip after wiping a short (<20 char) draft.
|
||||
@@ -0,0 +1,42 @@
|
||||
name: undo-tip-short-terminal-no-show
|
||||
description: >
|
||||
No-show edge: on a short terminal (<= SHORT_TERMINAL_ROWS rows) the
|
||||
renderability gate refuses the banner, so even a substantial wipe shows no
|
||||
undo tip — counts/TTL never burn on a row the user cannot see.
|
||||
terminal:
|
||||
rows: 32
|
||||
cols: 100
|
||||
environment:
|
||||
env:
|
||||
- key: KIGI_CONTEXTUAL_HINTS
|
||||
value: "1"
|
||||
mock:
|
||||
response: "unused"
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: focus_prompt
|
||||
# Build a substantial draft in the agent view while the terminal is tall.
|
||||
- action: type_text
|
||||
text: "ddddddddddddddddddddddddd"
|
||||
- action: wait
|
||||
millis: 400
|
||||
- action: assert_contains
|
||||
text: "dddddddddd"
|
||||
# Shrink to a short terminal: the banner row can no longer paint.
|
||||
- action: resize
|
||||
rows: 14
|
||||
cols: 100
|
||||
- action: wait
|
||||
millis: 300
|
||||
- action: keys
|
||||
keys: "<C-u>"
|
||||
- action: wait
|
||||
millis: 700
|
||||
- action: assert_not_contains
|
||||
text: "to undo"
|
||||
- action: assert_running
|
||||
- action: screenshot
|
||||
name: undo-tip-short-terminal
|
||||
note: No tip on a short terminal even after a substantial wipe.
|
||||
@@ -0,0 +1,92 @@
|
||||
name: vim-modal-command-palette
|
||||
description: >
|
||||
Type-to-find pickers default to INPUT on open while staying vim-toggleable.
|
||||
With vim-mode ON, the command palette opens in INPUT mode: a letter filters the
|
||||
list immediately (no-match query shows "No matches"). Esc clears the query
|
||||
(command palette's modal chrome clears search text first); a second Esc drops to
|
||||
NAV mode where letters no longer filter; `i` re-enters INPUT (a letter filters
|
||||
again). The footer surfaces an `i search` hint in NAV mode (discoverability) and
|
||||
hides it in INPUT mode. Drives the real pager via the scripted ptyctl runner.
|
||||
terminal:
|
||||
rows: 40
|
||||
cols: 110
|
||||
mock:
|
||||
response: "SCRIPTED_VIM_MODAL acknowledged."
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
# Promote welcome → session so we are unambiguously on the agent screen.
|
||||
- action: type_text
|
||||
text: "hello"
|
||||
- action: keys
|
||||
keys: "<Enter>"
|
||||
- action: wait_for_text
|
||||
text: SCRIPTED_VIM_MODAL
|
||||
timeout_ms: 30000
|
||||
# Turn vim-mode ON (the setting that gates vim_normal_first at the call sites).
|
||||
- action: type_text
|
||||
text: "/vim-mode"
|
||||
- action: keys
|
||||
keys: "<Enter>"
|
||||
- action: wait_for_text
|
||||
text: "Vim mode: on"
|
||||
timeout_ms: 10000
|
||||
# Open the command palette (Ctrl+P) — opens in INPUT mode (type-to-find).
|
||||
- action: keys
|
||||
keys: "<C-p>"
|
||||
- action: wait_for_text
|
||||
text: Commands
|
||||
timeout_ms: 10000
|
||||
- action: assert_contains
|
||||
text: New Session
|
||||
# INPUT mode on open: the footer does NOT advertise the `i` search hint.
|
||||
- action: assert_not_contains
|
||||
text: i search
|
||||
# INPUT mode on open: a letter filters immediately. "xyz" matches nothing → "No matches".
|
||||
- action: type_text
|
||||
text: "xyz"
|
||||
- action: wait
|
||||
millis: 200
|
||||
- action: assert_contains
|
||||
text: No matches
|
||||
# Esc clears the query (command palette chrome) → list restored, still input.
|
||||
- action: keys
|
||||
keys: "<Esc>"
|
||||
- action: wait
|
||||
millis: 200
|
||||
- action: assert_not_contains
|
||||
text: No matches
|
||||
- action: assert_contains
|
||||
text: New Session
|
||||
# Second Esc (empty query) drops to NAV mode via the picker's vim Esc.
|
||||
- action: keys
|
||||
keys: "<Esc>"
|
||||
- action: wait
|
||||
millis: 200
|
||||
# NAV mode: the footer now advertises `i search` so users discover how to type.
|
||||
- action: assert_contains
|
||||
text: i search
|
||||
# NAV mode: typing a letter must NOT filter (list stays intact, no "No matches").
|
||||
- action: type_text
|
||||
text: "xyz"
|
||||
- action: wait
|
||||
millis: 200
|
||||
- action: assert_not_contains
|
||||
text: No matches
|
||||
- action: assert_contains
|
||||
text: New Session
|
||||
# `i` re-enters INPUT mode; typing now filters again → "No matches".
|
||||
- action: keys
|
||||
keys: "i"
|
||||
- action: type_text
|
||||
text: "xyz"
|
||||
- action: wait
|
||||
millis: 200
|
||||
- action: assert_contains
|
||||
text: No matches
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
- action: screenshot
|
||||
name: vim-modal-command-palette
|
||||
note: Command palette input-default, Esc clears query, Esc→nav (no filter), i→input.
|
||||
@@ -0,0 +1,27 @@
|
||||
name: welcome-screen
|
||||
description: Validate that the real pager boots to the welcome screen and produces visual artifacts.
|
||||
terminal:
|
||||
rows: 32
|
||||
cols: 100
|
||||
mock:
|
||||
response: "SCRIPTED_WELCOME_RESPONSE from the mock server."
|
||||
steps:
|
||||
- action: wait_for_text
|
||||
text: Quit
|
||||
timeout_ms: 20000
|
||||
- action: assert_contains
|
||||
text: Quit
|
||||
- action: assert_not_contains
|
||||
text: panicked
|
||||
- action: screenshot
|
||||
name: welcome
|
||||
note: Welcome screen after startup.
|
||||
- action: resize
|
||||
rows: 28
|
||||
cols: 90
|
||||
- action: wait
|
||||
millis: 250
|
||||
- action: assert_running
|
||||
- action: screenshot
|
||||
name: resized-welcome
|
||||
note: Welcome screen after resize.
|
||||
Reference in New Issue
Block a user