diff --git a/.agents/specs/composer.md b/.agents/specs/composer.md index 5ce2e01..fae1cd2 100644 --- a/.agents/specs/composer.md +++ b/.agents/specs/composer.md @@ -56,13 +56,16 @@ instant, so they never move the semantic revision. Elapsed counts the primary's opens until it ends: it carries through the gaps between steps and stops while an approval or a question holds the turn on the user. Quiet appears after five seconds with nothing from the primary's own events, never a child's, and says nothing while the turn waits on the user. A short -row drops effort, then quiet, then elapsed. The row opens with the mark: the Nerd Font's circle, -rounded square and square, outline then filled, in blue for the agent's own work and the -server-tool colour while a provider-run search is the work, standing still in the action-required -colour while approval is required; it moves on the one visible-only clock (MOT-1, MOT-3). The +row drops effort, then quiet, then elapsed. The row opens with the mark, from the Nerd Font's one +Material Design family: a dot grows into a circle, turns into a rounded square and a square, spins +into a diamond and shrinks away through a smaller one, lingering on the whole shapes. It is blue +for the agent's own work and the server-tool colour while a provider-run search is the work, and +stands still as the whole circle in the action-required colour while approval is required; it +moves on the one visible-only clock (MOT-1, MOT-3). The row's right end holds the selection note (SEL-2) and the attention pill (ATT-1); while its rows are closed the selection note rides the status row instead (ui-ux §input). Rejected: current work on -the composer's rule, which mixed the agent's doing with the user's typing. +the composer's rule, which mixed the agent's doing with the user's typing; and a mark that changed +shape at one size, or stepped evenly back along its own path, which the user found rigid. **COM-6 — Input selection names editable source.** A click places the caret; dragging retains a grapheme-boundary anchor, paints the source range, and release copies it through SEL-4. Typing or diff --git a/.agents/specs/motion.md b/.agents/specs/motion.md index de0858d..37a6ba2 100644 --- a/.agents/specs/motion.md +++ b/.agents/specs/motion.md @@ -27,4 +27,4 @@ clock was doing. The phase is a count, not a time: renderers derive their frame from it, so two moving things on screen stay in step. The effort rail's marker and the activity line's mark each complete a cycle every -1.6 s, the mark at three phases a frame over eight frames. +1.6 s, the mark over seven frames holding unequal numbers of its 24 phases. diff --git a/.agents/spikes/conversation-chrome/README.md b/.agents/spikes/conversation-chrome/README.md index a64ddb8..2c96da8 100644 --- a/.agents/spikes/conversation-chrome/README.md +++ b/.agents/spikes/conversation-chrome/README.md @@ -9,14 +9,14 @@ Every decision below was made by the user on a rendered frame, 2026-09-21 and 2026-09-22. The generators are in [`mockups/`](./mockups/); each writes one HTML page to `plexmaton-conversation-chrome/` under the system temporary directory, outside this corpus, and -`spin.py`, `logo_term.py` and `cellsize.py` run in a terminal. Regenerate rather than trusting a description of them. +`spin.py`, `mark-lively.py`, `logo_term.py` and `cellsize.py` run in a terminal. Regenerate rather than trusting a description of them. ## What the user chose | Surface | Chosen | Rejected on sight | | --- | --- | --- | | Activity line composition | mark, label, muted `· 32s · high effort`, `· quiet for 31s` once nothing has arrived; no token count | a red stall tell; estimated tokens | -| Activity mark | N2, the Nerd Font's circle, rounded square and square, outline then filled: chosen 2026-09-22 in a kitty window beside the Unicode cycles; the user asked for the filled half to keep it simple yet ever changing | C2′ and C2″, which shook in the user's terminal because mixed-size Unicode shapes centre differently; filled squares; braille; quarter circles; Claude Code's star | +| Activity mark | H: a dot grows into a circle, turns into a rounded square and a square, spins into a diamond and shrinks away through a smaller one, lingering on whole shapes, 1.6 s a cycle; chosen 2026-09-23 in kitty (`mark-lively.py`) after the user found N2 rigid and wanted it to grow from a point | N2, the circle, rounded square and square outline then filled, chosen 2026-09-22 and later found rigid; one size changing shape, and even steps retracing their path, likewise; a slice-by-slice sweep, which reads as progress; C2′ and C2″, which shook because mixed-size Unicode shapes centre differently; filled squares; braille; quarter circles; Claude Code's star | | User message | band of lifted ground with a blue `›`, continuation indented | the accent bar; a bare prefix; a hairline bar; a band alone | | Mark placement | centred in the empty conversation, name beneath, gone with the first message | a header or drawer slot, not yet asked for | | Mark motion | K4 as the base: frame breathes between weights and drifts between palette slots; centre morphs circle, ring, rounded square, square, solid and outline | K1 to K3 as they stood | @@ -40,8 +40,16 @@ kitty answers `CSI 16 t` with the cell in pixels; `cellsize.py` shows the reply Material Design glyphs used, all present at one advance in Sarasa Term SC Nerd and in Symbols Nerd Font Mono: `circle-small` F09DF, `circle-medium` F09DE, `circle` F0765, `circle-outline` F0766, `square` F0763, `square-outline` F0764, `square-rounded` F14FB, `square-rounded-outline` F14FC, -`radiobox-marked` F043E, `record-circle` F0FEC, `circle-double` F0E95. The product already draws -its header, tally and copy glyphs from this set. +`radiobox-marked` F043E, `record-circle` F0FEC, `circle-double` F0E95, `rhombus` F070B, +`rhombus-medium` F0A10, `square-small` F0A15, `square-medium` F0A13. The product already draws its +header, tally and copy glyphs from this set. + +Measured in Maple Mono NF CN, in units of a 1000-unit em on a 600-unit cell: the family's filled +shapes come at 168, 332 and 461 (the medium diamond), then 750 to 832, with nothing between, so a +mark grows in steps rather than smoothly. Its small and medium sizes and its squares share one +centre; the whole circle and diamond sit 41 units right of it, an offset nobody has seen move. +Maple Mono's own `·`, `•`, `▪`, `●` and `■` centre on the cell instead, 75 to 116 units left of +the family's, so they cannot join its cycle; `▢`, `◼` and `⬤` are not in the font and fall back. ## What the other CLIs do, read from their source diff --git a/.agents/spikes/conversation-chrome/mockups/mark-lively.py b/.agents/spikes/conversation-chrome/mockups/mark-lively.py new file mode 100644 index 0000000..1854e9f --- /dev/null +++ b/.agents/spikes/conversation-chrome/mockups/mark-lively.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +"""C made lively: growth, a turn and a sweep, from one Material Design family. Ctrl-C to stop. + +Every glyph here shares the family's centring, and each frame lasts a whole number of the motion +clock's 67 ms phases; cycles are 24 phases (1.6 s) or 16 (1.07 s), both of which the product's +clock can drive unchanged. +""" +import sys, time + +TEXT, BLUE, MUTED, RESET, REV = ("\x1b[38;2;230;233;240m", "\x1b[38;2;130;180;240m", + "\x1b[38;2;142;162;196m", "\x1b[0m", "\x1b[7m") +PHASE_MS = 1000 / 15 +G = dict(c_small="\U000F09DF", c_medium="\U000F09DE", circle="\U000F0765", + square="\U000F0763", rounded="\U000F14FB", + rhombus="\U000F070B", r_medium="\U000F0A10") +G.update({f"slice{n}": chr(0xF0A9D + n) for n in range(1, 9)}) + +SLICES = [(f"slice{n}", 1) for n in range(1, 9)] +CANDIDATES = [ + ("E liked: C with pace", [("c_small", 3), ("c_medium", 1), ("circle", 5), ("rounded", 2), + ("square", 5), ("rounded", 2), ("circle", 5), ("c_medium", 1)]), + ("H grow, turn, spin out · 1.6 s", [("c_small", 3), ("c_medium", 1), ("circle", 6), ("rounded", 2), + ("square", 5), ("rhombus", 4), ("r_medium", 3)]), + ("K spark · 1.6 s", [("c_small", 3), ("c_medium", 1), ("r_medium", 2), ("rhombus", 4), + ("square", 4), ("rounded", 2), ("circle", 5), ("c_medium", 3)]), + ("I sweep · 1.6 s", [("c_small", 4), ("c_medium", 1), *SLICES, ("circle", 8), ("c_medium", 3)]), + ("H' grow, turn, spin out · 1.07 s", [("c_small", 2), ("c_medium", 1), ("circle", 4), ("rounded", 1), + ("square", 3), ("rhombus", 3), ("r_medium", 2)]), + ("K' spark · 1.07 s", [("c_small", 2), ("c_medium", 1), ("r_medium", 1), ("rhombus", 3), + ("square", 3), ("rounded", 1), ("circle", 3), ("c_medium", 2)]), + ("I' sweep · 1.07 s", [("c_small", 2), ("c_medium", 1), *SLICES, ("circle", 4), ("c_medium", 1)]), +] + + +def frame_at(frames, phase): + phase %= sum(hold for _, hold in frames) + for glyph, hold in frames: + if phase < hold: + return glyph + phase -= hold + raise AssertionError("unreachable") + + +def strip(): + print(f"\n{TEXT}Frames in order, each in its own cell; the number is how many 67 ms phases it holds:{RESET}\n") + for name, frames in CANDIDATES: + cells = " ".join(f"{REV}{G[glyph]}{RESET}{MUTED}{hold}{RESET}" for glyph, hold in frames) + print(f" {MUTED}{name:38}{RESET} {cells}") + print(f"\n{TEXT}As the activity line:{RESET}\n") + + +def animate(): + sys.stdout.write("\x1b[?25l") + start = time.time() + try: + while True: + elapsed_ms = (time.time() - start) * 1000 + phase = int(elapsed_ms // PHASE_MS) + for name, frames in CANDIDATES: + glyph = G[frame_at(frames, phase)] + seconds = 12 + int(elapsed_ms // 1000) + sys.stdout.write(f" {BLUE}{glyph}{RESET} {TEXT}Thinking…{RESET}" + f"{MUTED} · {seconds}s · max effort{RESET} {MUTED}{name}{RESET}\x1b[K\n\n") + sys.stdout.write(f"\x1b[{2 * len(CANDIDATES)}A") + sys.stdout.flush() + time.sleep(0.01) + except KeyboardInterrupt: + pass + finally: + sys.stdout.write(f"\x1b[{2 * len(CANDIDATES)}B\x1b[?25h{RESET}\n") + + +if __name__ == "__main__": + for name, frames in CANDIDATES: + assert sum(hold for _, hold in frames) in (16, 24), name + strip() + animate() diff --git a/crates/plexmaton-tui/frames/agents-strip-narrow.txt b/crates/plexmaton-tui/frames/agents-strip-narrow.txt index cc21227..08bab43 100644 --- a/crates/plexmaton-tui/frames/agents-strip-narrow.txt +++ b/crates/plexmaton-tui/frames/agents-strip-narrow.txt @@ -16,7 +16,7 @@ │transcript remains interactive. │ │ │ │received from Agent B · UI study · Routing stays centrali…│ -│󰝦 Thinking… ( !2 ) │ +│󰧟 Thinking… ( !2 ) │ │ │ ── Message ───────────────────────────────────────────────── Type a message · ⇥ to focus diff --git a/crates/plexmaton-tui/frames/agents-strip-one-column.txt b/crates/plexmaton-tui/frames/agents-strip-one-column.txt index 5d4a6f7..ab4d516 100644 --- a/crates/plexmaton-tui/frames/agents-strip-one-column.txt +++ b/crates/plexmaton-tui/frames/agents-strip-one-column.txt @@ -16,7 +16,7 @@ │independently; this transcript remains interactive. │ │ │ │received from Agent B · UI study · Routing stays centralized and z-ordered. │ -│󰝦 Thinking… ( !2 ) │ +│󰧟 Thinking… ( !2 ) │ │ │ ── Message ──────────────────────────────────────────────────────────────────────────────────── Type a message · ⇥ to focus diff --git a/crates/plexmaton-tui/frames/agents-strip-tally.txt b/crates/plexmaton-tui/frames/agents-strip-tally.txt index 447c368..2acc931 100644 --- a/crates/plexmaton-tui/frames/agents-strip-tally.txt +++ b/crates/plexmaton-tui/frames/agents-strip-tally.txt @@ -12,7 +12,7 @@ │independently; this transcript remains interactive. │ │ │ │received from Agent B · UI study · Routing stays centralized and z-ordered. │ -│󰝦 Thinking… │ +│󰧟 Thinking… │ │ │ ── Message ──────────────────────────────────────────────────────────────────────────────────── Type a message · ⇥ to focus diff --git a/crates/plexmaton-tui/frames/agents-strip-two-columns.txt b/crates/plexmaton-tui/frames/agents-strip-two-columns.txt index 6c5b002..518646e 100644 --- a/crates/plexmaton-tui/frames/agents-strip-two-columns.txt +++ b/crates/plexmaton-tui/frames/agents-strip-two-columns.txt @@ -16,7 +16,7 @@ │interactive. ││ │ │ ││ │ │received from Agent B · UI study · Routing stays centralized and z-…││ │ -│󰝦 Thinking… ( !2 ) ││ │ +│󰧟 Thinking… ( !2 ) ││ │ │ ││ │ ── Message ───────────────────────────────────────────────────────────│ │ Type a message · ⇥ to focus │Controller unavailable · input locked │ diff --git a/crates/plexmaton-tui/frames/approval-medium.txt b/crates/plexmaton-tui/frames/approval-medium.txt index f8a7e70..dd6209a 100644 --- a/crates/plexmaton-tui/frames/approval-medium.txt +++ b/crates/plexmaton-tui/frames/approval-medium.txt @@ -21,7 +21,7 @@ │independently; this transcript remains interactive. │ │ │ │received from Agent B · UI study · Routing stays centralized and z-ordered. │ -│󰝦 Thinking… ( !1 ) │ +│󰧟 Thinking… ( !1 ) │ │ │ ── Approval required · edit ─────────────────────────────────────────────────────────────────── diff --git a/crates/plexmaton-tui/frames/approval-wide.txt b/crates/plexmaton-tui/frames/approval-wide.txt index 13c5cc4..834963e 100644 --- a/crates/plexmaton-tui/frames/approval-wide.txt +++ b/crates/plexmaton-tui/frames/approval-wide.txt @@ -21,7 +21,7 @@ │transcript remains interactive. ││ │ │ ││ │ │received from Agent B · UI study · Routing stays centrali…││ │ -│󰝦 Thinking… ( !1 ) ││ │ +│󰧟 Thinking… ( !1 ) ││ │ │ ││ │ ── Approval required · edit ────────────────────────────────│ │ │ │ diff --git a/crates/plexmaton-tui/frames/canonical-medium.txt b/crates/plexmaton-tui/frames/canonical-medium.txt index 7494f77..7199798 100644 --- a/crates/plexmaton-tui/frames/canonical-medium.txt +++ b/crates/plexmaton-tui/frames/canonical-medium.txt @@ -32,7 +32,7 @@ │independently; this transcript remains interactive. │ │ │ │received from Agent B · UI study · Routing stays centralized and z-ordered. │ -│󰝦 Thinking… ( !1 ) │ +│󰧟 Thinking… ( !1 ) │ │ │ ── Message ──────────────────────────────────────────────────────────────────────────────────── Type a message · ⇥ to focus diff --git a/crates/plexmaton-tui/frames/canonical-narrow.txt b/crates/plexmaton-tui/frames/canonical-narrow.txt index 8692371..907e5b4 100644 --- a/crates/plexmaton-tui/frames/canonical-narrow.txt +++ b/crates/plexmaton-tui/frames/canonical-narrow.txt @@ -32,7 +32,7 @@ │transcript remains interactive. │ │ │ │received from Agent B · UI study · Routing stays centrali…│ -│󰝦 Thinking… ( !1 ) │ +│󰧟 Thinking… ( !1 ) │ │ │ ── Message ───────────────────────────────────────────────── Type a message · ⇥ to focus diff --git a/crates/plexmaton-tui/frames/canonical-wide.txt b/crates/plexmaton-tui/frames/canonical-wide.txt index 36e9ad4..12852bc 100644 --- a/crates/plexmaton-tui/frames/canonical-wide.txt +++ b/crates/plexmaton-tui/frames/canonical-wide.txt @@ -32,7 +32,7 @@ │remains interactive. │ │ │ │received from Agent B · UI study · Routing stays centralized and z-ordered. │ -│󰝦 Thinking… ( !1 ) │ +│󰧟 Thinking… ( !1 ) │ │ │ ── Message ───────────────────────────────────────────────────────────────────────────────────────────────────────────── Type a message · ⇥ to focus diff --git a/crates/plexmaton-tui/frames/current-work-responding-medium.txt b/crates/plexmaton-tui/frames/current-work-responding-medium.txt index e2b6a7c..409397f 100644 --- a/crates/plexmaton-tui/frames/current-work-responding-medium.txt +++ b/crates/plexmaton-tui/frames/current-work-responding-medium.txt @@ -1,4 +1,4 @@ -│󰝦 Responding… ( !1 ) │ +│󰧟 Responding… ( !1 ) │ │ │ ── Message ──────────────────────────────────────────────────────────────────────────────────── Type a message · ⇥ to focus diff --git a/crates/plexmaton-tui/frames/current-work-responding-narrow.txt b/crates/plexmaton-tui/frames/current-work-responding-narrow.txt index ca724fc..a6b78b6 100644 --- a/crates/plexmaton-tui/frames/current-work-responding-narrow.txt +++ b/crates/plexmaton-tui/frames/current-work-responding-narrow.txt @@ -1,4 +1,4 @@ -│󰝦 Responding… ( !1 ) │ +│󰧟 Responding… ( !1 ) │ │ │ ── Message ───────────────────────────────────────────────── Type a message · ⇥ to focus diff --git a/crates/plexmaton-tui/frames/current-work-responding-wide.txt b/crates/plexmaton-tui/frames/current-work-responding-wide.txt index 9a60619..1b31b60 100644 --- a/crates/plexmaton-tui/frames/current-work-responding-wide.txt +++ b/crates/plexmaton-tui/frames/current-work-responding-wide.txt @@ -1,4 +1,4 @@ -│󰝦 Responding… ( !1 ) │ +│󰧟 Responding… ( !1 ) │ │ │ ── Message ───────────────────────────────────────────────────────────────────────────────────────────────────────────── Type a message · ⇥ to focus diff --git a/crates/plexmaton-tui/frames/current-work-running-tool-medium.txt b/crates/plexmaton-tui/frames/current-work-running-tool-medium.txt index 18ba765..271e76d 100644 --- a/crates/plexmaton-tui/frames/current-work-running-tool-medium.txt +++ b/crates/plexmaton-tui/frames/current-work-running-tool-medium.txt @@ -1,4 +1,4 @@ -│󰝦 Running read_file… ( !1 ) │ +│󰧟 Running read_file… ( !1 ) │ │ │ ── Message ──────────────────────────────────────────────────────────────────────────────────── Type a message · ⇥ to focus diff --git a/crates/plexmaton-tui/frames/current-work-running-tool-narrow.txt b/crates/plexmaton-tui/frames/current-work-running-tool-narrow.txt index 46bcb5f..8b9820a 100644 --- a/crates/plexmaton-tui/frames/current-work-running-tool-narrow.txt +++ b/crates/plexmaton-tui/frames/current-work-running-tool-narrow.txt @@ -1,4 +1,4 @@ -│󰝦 Running read_file… ( !1 ) │ +│󰧟 Running read_file… ( !1 ) │ │ │ ── Message ───────────────────────────────────────────────── Type a message · ⇥ to focus diff --git a/crates/plexmaton-tui/frames/current-work-running-tool-wide.txt b/crates/plexmaton-tui/frames/current-work-running-tool-wide.txt index 2acc265..7219b59 100644 --- a/crates/plexmaton-tui/frames/current-work-running-tool-wide.txt +++ b/crates/plexmaton-tui/frames/current-work-running-tool-wide.txt @@ -1,4 +1,4 @@ -│󰝦 Running read_file… ( !1 ) │ +│󰧟 Running read_file… ( !1 ) │ │ │ ── Message ───────────────────────────────────────────────────────────────────────────────────────────────────────────── Type a message · ⇥ to focus diff --git a/crates/plexmaton-tui/frames/drawer-medium.txt b/crates/plexmaton-tui/frames/drawer-medium.txt index e7e473e..8524b7d 100644 --- a/crates/plexmaton-tui/frames/drawer-medium.txt +++ b/crates/plexmaton-tui/frames/drawer-medium.txt @@ -32,7 +32,7 @@ │independently; this transcript remains interactive. │ │ │ │received from Agent B · UI study · Routing stays centralized and z-ordered. │ -│󰝦 Thinking… ( !1 ) │ +│󰧟 Thinking… ( !1 ) │ │ │ ── Message ──────────────────────────────────────────────────────────────────────────────────── Type a message · ⇥ to focus diff --git a/crates/plexmaton-tui/frames/drawer-narrow.txt b/crates/plexmaton-tui/frames/drawer-narrow.txt index a748154..549563d 100644 --- a/crates/plexmaton-tui/frames/drawer-narrow.txt +++ b/crates/plexmaton-tui/frames/drawer-narrow.txt @@ -32,7 +32,7 @@ │transcript remains interactive. │ │ │ │received from Agent B · UI study · Routing stays centrali…│ -│󰝦 Thinking… ( !1 ) │ +│󰧟 Thinking… ( !1 ) │ │ │ ── Message ───────────────────────────────────────────────── Type a message · ⇥ to focus diff --git a/crates/plexmaton-tui/frames/drawer-ultrawide.txt b/crates/plexmaton-tui/frames/drawer-ultrawide.txt index f738933..20008de 100644 --- a/crates/plexmaton-tui/frames/drawer-ultrawide.txt +++ b/crates/plexmaton-tui/frames/drawer-ultrawide.txt @@ -32,7 +32,7 @@ │I will inspect the workspace and delegate the focused UI study. Agent B is running independently; this transcript remains interactive. │ │ │ │received from Agent B · UI study · Routing stays centralized and z-ordered. │ -│󰝦 Thinking… ( !1 ) │ +│󰧟 Thinking… ( !1 ) │ │ │ ── Message ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Type a message · ⇥ to focus diff --git a/crates/plexmaton-tui/frames/drawer-wide.txt b/crates/plexmaton-tui/frames/drawer-wide.txt index b5d72e6..36e4608 100644 --- a/crates/plexmaton-tui/frames/drawer-wide.txt +++ b/crates/plexmaton-tui/frames/drawer-wide.txt @@ -32,7 +32,7 @@ │remains interactive. │ │ │ │received from Agent B · UI study · Routing stays centralized and z-ordered. │ -│󰝦 Thinking… ( !1 ) │ +│󰧟 Thinking… ( !1 ) │ │ │ ── Message ───────────────────────────────────────────────────────────────────────────────────────────────────────────── Type a message · ⇥ to focus diff --git a/crates/plexmaton-tui/frames/input-queue-medium.txt b/crates/plexmaton-tui/frames/input-queue-medium.txt index 08eec72..e1b7b5d 100644 --- a/crates/plexmaton-tui/frames/input-queue-medium.txt +++ b/crates/plexmaton-tui/frames/input-queue-medium.txt @@ -27,7 +27,7 @@ │received from Agent B · UI study · Routing stays centralized and z-ordered. │ │ │ │ │ -│󰝦 Responding… ( !1 ) │ +│󰧟 Responding… ( !1 ) │ │ │ ── Waiting to send · 2 ──────────────────────────────────────────────────────────────────────── Sends when this turn ends diff --git a/crates/plexmaton-tui/frames/input-queue-narrow.txt b/crates/plexmaton-tui/frames/input-queue-narrow.txt index 5f49fa4..6b116ef 100644 --- a/crates/plexmaton-tui/frames/input-queue-narrow.txt +++ b/crates/plexmaton-tui/frames/input-queue-narrow.txt @@ -27,7 +27,7 @@ │received from Agent B · UI study · Routing stays centrali…│ │ │ │ │ -│󰝦 Responding… ( !1 ) │ +│󰧟 Responding… ( !1 ) │ │ │ ── Waiting to send · 2 ───────────────────────────────────── Sends when this turn ends diff --git a/crates/plexmaton-tui/frames/input-queue-wide.txt b/crates/plexmaton-tui/frames/input-queue-wide.txt index 236215e..7fe7218 100644 --- a/crates/plexmaton-tui/frames/input-queue-wide.txt +++ b/crates/plexmaton-tui/frames/input-queue-wide.txt @@ -27,7 +27,7 @@ │received from Agent B · UI study · Routing stays centralized and z-ordered. │ │ │ │ │ -│󰝦 Responding… ( !1 ) │ +│󰧟 Responding… ( !1 ) │ │ │ ── Waiting to send · 2 ───────────────────────────────────────────────────────────────────────────────────────────────── Sends when this turn ends diff --git a/crates/plexmaton-tui/frames/server-tool-medium.txt b/crates/plexmaton-tui/frames/server-tool-medium.txt index 7b94438..8671fae 100644 --- a/crates/plexmaton-tui/frames/server-tool-medium.txt +++ b/crates/plexmaton-tui/frames/server-tool-medium.txt @@ -32,7 +32,7 @@ │Rust 1.98.1 is the latest stable release. It is a point release over 1.98.0, and the │ │release post on the Rust blog lists what it fixes. │ │ │ -│󰝦 Running web_search… │ +│󰧟 Running web_search… │ │ │ ── Message ──────────────────────────────────────────────────────────────────────────────────── Type a message · ⇥ to focus diff --git a/crates/plexmaton-tui/frames/server-tool-narrow.txt b/crates/plexmaton-tui/frames/server-tool-narrow.txt index a413dde..fbbfec0 100644 --- a/crates/plexmaton-tui/frames/server-tool-narrow.txt +++ b/crates/plexmaton-tui/frames/server-tool-narrow.txt @@ -32,7 +32,7 @@ │point release over 1.98.0, and the release post on │ │the Rust blog lists what it fixes. │ │ │ -│󰝦 Running web_search… │ +│󰧟 Running web_search… │ │ │ ── Message ───────────────────────────────────────────────── Type a message · ⇥ to focus diff --git a/crates/plexmaton-tui/frames/server-tool-open-wide.txt b/crates/plexmaton-tui/frames/server-tool-open-wide.txt index b45dbb2..b68038e 100644 --- a/crates/plexmaton-tui/frames/server-tool-open-wide.txt +++ b/crates/plexmaton-tui/frames/server-tool-open-wide.txt @@ -32,7 +32,7 @@ │Rust 1.98.1 is the latest stable release. It is a point release over 1.98.0, and the release post on the Rust │ │blog lists what it fixes. │ │ │ -│󰝦 Running web_search… │ +│󰧟 Running web_search… │ │ │ ── Message ───────────────────────────────────────────────────────────────────────────────────────────────────────────── Type a message · ⇥ to focus diff --git a/crates/plexmaton-tui/frames/server-tool-wide.txt b/crates/plexmaton-tui/frames/server-tool-wide.txt index 96ec127..51448b3 100644 --- a/crates/plexmaton-tui/frames/server-tool-wide.txt +++ b/crates/plexmaton-tui/frames/server-tool-wide.txt @@ -32,7 +32,7 @@ │Rust 1.98.1 is the latest stable release. It is a point release over 1.98.0, and the release post on the Rust │ │blog lists what it fixes. │ │ │ -│󰝦 Running web_search… │ +│󰧟 Running web_search… │ │ │ ── Message ───────────────────────────────────────────────────────────────────────────────────────────────────────────── Type a message · ⇥ to focus diff --git a/crates/plexmaton-tui/frames/skill-diagnostic-medium.txt b/crates/plexmaton-tui/frames/skill-diagnostic-medium.txt index 91f239e..d9df6fe 100644 --- a/crates/plexmaton-tui/frames/skill-diagnostic-medium.txt +++ b/crates/plexmaton-tui/frames/skill-diagnostic-medium.txt @@ -32,7 +32,7 @@ │ │ │received from Agent B · UI study · Routing stays centralized and z-ordered. │ │[!] skill · failed │ -│󰝦 Thinking… ( !1 ) │ +│󰧟 Thinking… ( !1 ) │ │ │ ── Message ──────────────────────────────────────────────────────────────────────────────────── $review check this change diff --git a/crates/plexmaton-tui/frames/skill-diagnostic-narrow.txt b/crates/plexmaton-tui/frames/skill-diagnostic-narrow.txt index 04a74ee..e46647c 100644 --- a/crates/plexmaton-tui/frames/skill-diagnostic-narrow.txt +++ b/crates/plexmaton-tui/frames/skill-diagnostic-narrow.txt @@ -32,7 +32,7 @@ │ │ │received from Agent B · UI study · Routing stays centrali…│ │[!] skill · failed │ -│󰝦 Thinking… ( !1 ) │ +│󰧟 Thinking… ( !1 ) │ │ │ ── Message ───────────────────────────────────────────────── $review check this change diff --git a/crates/plexmaton-tui/frames/skill-diagnostic-wide.txt b/crates/plexmaton-tui/frames/skill-diagnostic-wide.txt index 248f012..0f05d6e 100644 --- a/crates/plexmaton-tui/frames/skill-diagnostic-wide.txt +++ b/crates/plexmaton-tui/frames/skill-diagnostic-wide.txt @@ -32,7 +32,7 @@ │ │ │received from Agent B · UI study · Routing stays centralized and z-ordered. │ │[!] skill · failed │ -│󰝦 Thinking… ( !1 ) │ +│󰧟 Thinking… ( !1 ) │ │ │ ── Message ───────────────────────────────────────────────────────────────────────────────────────────────────────────── $review check this change diff --git a/crates/plexmaton-tui/frames/tool-open-medium.txt b/crates/plexmaton-tui/frames/tool-open-medium.txt index 958c11d..55b4826 100644 --- a/crates/plexmaton-tui/frames/tool-open-medium.txt +++ b/crates/plexmaton-tui/frames/tool-open-medium.txt @@ -32,7 +32,7 @@ │ │ 197 tests passed │ │ │ stderr: │ │ │ [empty] │ -│󰝦 Thinking… │ +│󰧟 Thinking… │ │ │ ── Message ──────────────────────────────────────────────────────────────────────────────────── Type a message · ⇥ to focus diff --git a/crates/plexmaton-tui/frames/tool-open-narrow.txt b/crates/plexmaton-tui/frames/tool-open-narrow.txt index 34298b2..a7fa1b9 100644 --- a/crates/plexmaton-tui/frames/tool-open-narrow.txt +++ b/crates/plexmaton-tui/frames/tool-open-narrow.txt @@ -32,7 +32,7 @@ │ │ 197 tests passed │ │ │ stderr: │ │ │ [empty] │ -│󰝦 Thinking… │ +│󰧟 Thinking… │ │ │ ── Message ───────────────────────────────────────────────── Type a message · ⇥ to focus diff --git a/crates/plexmaton-tui/frames/tool-open-wide.txt b/crates/plexmaton-tui/frames/tool-open-wide.txt index 753d15c..25ec687 100644 --- a/crates/plexmaton-tui/frames/tool-open-wide.txt +++ b/crates/plexmaton-tui/frames/tool-open-wide.txt @@ -32,7 +32,7 @@ │ │ 197 tests passed │ │ │ stderr: │ │ │ [empty] │ -│󰝦 Thinking… │ +│󰧟 Thinking… │ │ │ ── Message ───────────────────────────────────────────────────────────────────────────────────────────────────────────── Type a message · ⇥ to focus diff --git a/crates/plexmaton-tui/src/frames.rs b/crates/plexmaton-tui/src/frames.rs index 0479b25..7c53f8f 100644 --- a/crates/plexmaton-tui/src/frames.rs +++ b/crates/plexmaton-tui/src/frames.rs @@ -185,7 +185,7 @@ mod tests { let mut rows = drawn.lines(); let activity = rows.next().unwrap_or_default(); assert!( - activity.contains(&format!("{} {label}", crate::render::activity::MARK[0])), + activity.contains(&format!("{} {label}", crate::render::activity::mark(0))), "{name}: the accepted label is on the activity line: {activity:?}" ); assert!( diff --git a/crates/plexmaton-tui/src/render/activity.rs b/crates/plexmaton-tui/src/render/activity.rs index 5f8970d..c3b4fcd 100644 --- a/crates/plexmaton-tui/src/render/activity.rs +++ b/crates/plexmaton-tui/src/render/activity.rs @@ -13,27 +13,49 @@ use crate::{ theme::{Palette, Role}, }; -/// Outline, then filled, each through circle, rounded square, square and back. One icon family -/// from the Nerd Font the product already requires, so every frame centres alike. Rejected: -/// Unicode geometric shapes of mixed sizes, which the user watched shake in their terminal. -pub(crate) const MARK: [&str; 8] = [ - "\u{F0766}", // circle-outline - "\u{F14FC}", // square-rounded-outline - "\u{F0764}", // square-outline - "\u{F14FC}", - "\u{F0765}", // circle - "\u{F14FB}", // square-rounded - "\u{F0763}", // square - "\u{F14FB}", +/// A dot grows into a circle, turns into a rounded square and a square, spins into a diamond and +/// shrinks away through a smaller one, lingering on the whole shapes and passing quickly through +/// the small ones. One icon family from the Nerd Font the product already requires, whose small, +/// medium and whole sizes share one centre, so growing never shakes. Each frame holds a whole +/// number of the motion clock's phases, 24 in all: a cycle every 1.6 s, the effort rail's period. +/// Rejected: Unicode geometric shapes of mixed sizes, which the user watched shake in their +/// terminal; a change of shape at one size, and even steps retracing their path, which the user +/// found rigid; a slice-by-slice sweep, which reads as progress. +pub(crate) const MARK: [(&str, u16); 7] = [ + ("\u{F09DF}", 3), // circle-small + ("\u{F09DE}", 1), // circle-medium + ("\u{F0765}", 6), // circle + ("\u{F14FB}", 2), // square-rounded + ("\u{F0763}", 5), // square + ("\u{F070B}", 4), // rhombus + ("\u{F0A10}", 3), // rhombus-medium ]; -/// Three of the clock's fifteen phases a second per frame: 200 ms, so a cycle takes 1.6 s, the -/// effort rail's own period. -const PHASES_PER_FRAME: u16 = 3; +/// The phases one cycle of the mark takes. +const CYCLE: u16 = { + let mut total = 0; + let mut index = 0; + while index < MARK.len() { + total += MARK[index].1; + index += 1; + } + total +}; + +/// The whole circle, which the mark holds while it stands still for an approval: the first frame is +/// too small to be seen waiting. +pub(crate) const STILL: &str = "\u{F0765}"; /// The frame for a clock phase. pub(crate) fn mark(phase: u16) -> &'static str { - MARK[usize::from(phase / PHASES_PER_FRAME) % MARK.len()] + let mut left = phase % CYCLE; + for (frame, hold) in MARK { + if left < hold { + return frame; + } + left -= hold; + } + unreachable!("the phase is reduced modulo the cycle the holds sum to") } /// The activity line: what the agent is doing on the left; what the reader has selected and what @@ -94,7 +116,7 @@ pub(crate) fn activity_line( let mark = if moving { mark(state.motion_phase()) } else { - MARK[0] + STILL }; left.push(Span::styled(mark, palette.style(mark_role))); left.push(Span::raw(" ")); @@ -240,22 +262,30 @@ mod tests { use unicode_width::UnicodeWidthStr; - use super::{MARK, PHASES_PER_FRAME, mark}; + use super::{CYCLE, MARK, STILL, mark}; /// MOT-2: every frame is one cell, so the mark can never widen the row. #[test] fn mot_2_every_activity_mark_frame_is_one_cell() { - for frame in MARK { + for (frame, _) in MARK { assert_eq!(frame.width(), 1, "{frame:?}"); } + assert_eq!(STILL.width(), 1); } - /// MOT-1: the cycle divides the clock's, so the mark never jumps where the clock wraps. + /// MOT-1: the cycle divides the clock's, so the mark never jumps where the clock wraps, and + /// every frame is shown for the phases it holds. #[test] fn the_mark_cycle_divides_the_clock_cycle() { - let frames = u16::try_from(MARK.len()).expect("small"); - assert_eq!(480 % (frames * PHASES_PER_FRAME), 0); - assert_eq!(mark(0), mark(480 - 480 % (frames * PHASES_PER_FRAME))); - assert_ne!(mark(0), mark(PHASES_PER_FRAME)); + assert_eq!(CYCLE, 24, "one cycle every 1.6 s"); + assert_eq!(480 % CYCLE, 0); + assert_eq!(mark(0), mark(480 - 480 % CYCLE)); + let mut phase = 0; + for (frame, hold) in MARK { + for _ in 0..hold { + assert_eq!(mark(phase), frame, "phase {phase}"); + phase += 1; + } + } } } diff --git a/crates/plexmaton-tui/src/render/mod.rs b/crates/plexmaton-tui/src/render/mod.rs index cb39485..2a32e30 100644 --- a/crates/plexmaton-tui/src/render/mod.rs +++ b/crates/plexmaton-tui/src/render/mod.rs @@ -505,7 +505,7 @@ mod tests { .unwrap_or_else(|| panic!("the canonical scenario creates a primary agent")); let assert_activity = |state: &ViewState, expected: &str, role: Role| { let line = activity_line(state, &palette, 80, false); - let mark = super::activity::MARK[0]; + let mark = super::activity::mark(0); assert!( line.to_string().starts_with(&format!("{mark} {expected}")), "the mark then {expected:?} lead the activity line: {line}" @@ -555,8 +555,8 @@ mod tests { let line = activity_line(&approval.state, &palette, 80, false); assert!( line.to_string() - .starts_with(&format!("{} Approval required", super::activity::MARK[0])), - "approval stands still on the mark's first frame: {line}" + .starts_with(&format!("{} Approval required", super::activity::STILL)), + "approval stands still on the whole circle: {line}" ); assert_eq!(line.spans[0].style, palette.style(Role::ActionRequired)); assert_eq!(line.spans[2].style, palette.style(Role::ActionRequired));