Skip to content

feat(tui): follow the viewport with the pinned prompt header and jump on click - #6830

Merged
Hmbown merged 9 commits into
codewhale-hq:mainfrom
SparkofSpike:codex/tui-pinned-prompt-follow-jump
Oct 3, 2026
Merged

Hmbown merged 9 commits into
codewhale-hq:mainfrom
SparkofSpike:codex/tui-pinned-prompt-follow-jump

Conversation

@SparkofSpike

Copy link
Copy Markdown
Contributor

Summary

Follow-up to the pinned user-prompt header: it now tracks the turn the
viewport starts on instead of only the newest message, and clicking it
returns the viewport to the message it names.

  1. Per-turn hand-over. The header used to belong to the newest user
    message alone: scrolling up past that message dropped the header
    entirely, and it only came back once the viewport cleared the whole
    turn. It now belongs to whichever user message owns the content at the
    viewport top — the newest user message whose first rendered line sits
    above the viewport. The instant a newer prompt's first line reaches the
    top row, the header hands over to the previous turn's prompt in the same
    frame, with no blank window while scrolling across a turn boundary.
  2. Click to jump. The frame that paints the header records its rect and
    the message it names. A left click pins the transcript viewport to that
    message's first line (dropping pending wheel deltas and leaving the live
    tail); clicking outside the header row is left to the transcript
    selection path. The header registers as a hover target with a new
    translated "Jump to this message" label.

Changes

  • crates/tui/src/tui/widgets/mod.rs: scrolled_user_prompt_pin scans
    rendered lines newest-first for the first non-blank user-message start
    above top and returns the header line plus the owning message's
    original history index; the caller re-resolves the header against the
    final, row-reserved viewport.
  • crates/tui/src/tui/app.rs: ViewportState gains pinned_prompt_area /
    pinned_prompt_message; App::scroll_to_transcript_line mirrors the
    wheel/scrollbar jump path, and pinned_prompt_target_line resolves the
    recorded message against the click frame's layout.
  • crates/tui/src/tui/mouse_ui.rs: a left click inside the header rect
    jumps; clicks outside fall through to transcript selection.
  • crates/tui/src/tui/ui/frame.rs: hover registration so the header lights
    up under the pointer like every other clickable chrome; the agent-focus
    sampling branch drops the main session's header box.
  • crates/localization: new PinnedPromptJumpToMessage message id plus one
    string across all 15 shipped locale packs.

Review follow-up

Two independent reviews were posted on the fork PR (SparkofSpike#5), both
closing as mergeable; every finding was addressed before merge.

  • SpikeBot 003 found that a prompt opening on a blank line was picked as
    the pin target and then failed the first-line render check, dropping the
    header instead of handing over to an older message — prompts without a
    renderable first line are now skipped during the scan, with a regression
    test. It also asked for multi-line and non-identity collapsed_cell_map
    coverage, which was added, and confirmed that the header's read
    resolution against the click frame's layout is intentional.
  • SpikeBot 005 found that the agent-focus sampling path recorded the
    pinned header's hit box even though the focus pane paints no header (the
    focus banner row became an invisible click/hover target — now dropped),
    and that reserving the header row moved top down on the tail so a
    prompt whose first line was exactly the full-height top row could be
    hidden behind a header naming an older prompt (the header is now
    re-resolved against the final viewport, with a regression test). It also
    asked for the header to key off a message identity rather than a
    frame-bound line offset, and for an end-to-end render→click→land test —
    both are in.
  • SpikeBot 003 re-checked the follow-up commits and closed mergeable,
    verifying each fix point by point.

Base and CI note

  • This branch has merged upstream main (ad4325063), so it sits on the
    current tree — that commit is also the rustfmt fix for
    crates/tui/src/child_env.rs, which makes cargo fmt --all -- --check
    clean repository-wide.
  • The CI Lint job's clippy step may still fail on three findings that
    live on main and are untouched here: child_env.rs:718
    (chunks_exact constant chunk size), mcp.rs:290 and lib.rs:13797
    (needless_return). Every file this PR touches is clippy-clean under
    the CI allow list.

Type of Change

  • New feature (non-breaking change which adds functionality)

Testing

  • cargo fmt --all -- --check — clean repository-wide
  • cargo check -p codewhale-tui --all-targets — exit 0
  • cargo clippy -p codewhale-tui --all-targets --locked -- -D warnings -A clippy::uninlined_format_args -A clippy::too_many_arguments -A clippy::unnecessary_map_or — no findings in any file this change touches (the three remaining errors are the pre-existing ones noted above)
  • cargo test -p codewhale-tui --lib -- pin_helper → 7 passed; 0 failed (hand-over timing, blank-led skips, reserved-viewport repin, filtered-map resolution)
  • cargo test -p codewhale-tui --lib -- pinned_prompt → 6 passed; 0 failed (header layout & tail reservation, hit-box recording, click jump, click outside the row, hover registration, render→click→land)
  • cargo test -p codewhale-tui --lib -- hover_target → 2 passed; 0 failed
  • cargo test -p codewhale-localization → 52 passed; 0 failed (key-parity gates across all packs, including the new message id)
  • Verified TUI behavior manually if UI changes — the click-to-jump
    path was verified live; the hand-over timing was iterated on live
    session feedback and is pinned by the unit tests above.

Checklist

  • Updated docs or comments as needed (module docs describe the
    hand-over semantics and the click path)
  • Added or updated tests where relevant
  • Verified TUI behavior manually if UI changes
  • Harvested/co-authored credit uses a GitHub numeric noreply address
    (no external contribution in this change)

Related Issues

No-Issue: interaction follow-up to the pinned prompt header; no upstream
issue was opened for this.

Attribution

🤖 Generated by SpikeBot 000(CodeWhale-LOCAL)

SparkofSpike and others added 9 commits September 26, 2026 22:51
fix(compaction): survive a provider request-body 413 while making room
docs(i18n): complete the Tier-2 should-have docs for EPIC codewhale-hq#5482
The pinned user-prompt header used to track only the newest user message:
once that message left the viewport the header vanished, and it only came
back after the viewport cleared the whole turn. Resolve the header against
the content the viewport starts on instead -- the newest user message whose
first rendered line sits above `top` -- so crossing a turn boundary hands
the header over to the previous turn's prompt in the same frame, with no
blank window in between.

The helper returns the owning message's first rendered line so a later
slice can jump the viewport back to it.

Signed-off-by: Shizuku <2163018547@qq.com>
The pinned prompt header now records its painted rect and the target
message's first rendered line on the frame that draws it, so a left click
on the header returns the viewport to the message it names. The header
also registers as a hover target, and the new `PinnedPromptJumpToMessage`
label is translated for all 15 shipped locale packs.

The hit box only exists while mouse capture is on, and a click outside the
header row is left to the transcript selection path.

Signed-off-by: Shizuku <2163018547@qq.com>
Review follow-up (SpikeBot 003): a user message opening on a blank line
was picked as the pin target and then failed the first-line render check,
returning None instead of handing over to an older message -- a drop-out
on the very path this feature keeps covered. Prompts without a renderable
first line are now skipped during the scan.

Adds the coverage the review asked for: the scan keys off a message's
first rendered line rather than a body line, and filtered cell indices
resolve through the collapsed-cell map.

Signed-off-by: Shizuku <2163018547@qq.com>
Review follow-up (SpikeBot 005, posted on this PR):

- The agent-focus sampling path builds the main ChatWidget only for its
  ocean column, but that constructor recorded the pinned header's hit box
  while the focus pane paints no such header — the focus banner row became
  an invisible hot zone that answered clicks and hover. The focus branch
  now drops the stale box.
- Reserving the header row moves `top` down on the tail, so a prompt whose
  first line was exactly the full-height top row ended up hidden behind a
  header naming an older prompt. The header is now re-resolved against the
  final viewport, with a regression test for the reserved case.
- The header records the message (original history index) instead of a
  frame-bound line offset, and the click path re-resolves it against the
  click frame's layout; `scroll_to_transcript_line` shares the scrollbar
  path's tail expression.
- Adds the render -> click -> land end-to-end case the review asked for.

Signed-off-by: Shizuku <2163018547@qq.com>
@SparkofSpike
SparkofSpike requested a review from Hmbown as a code owner October 3, 2026 05:48
@Hmbown
Hmbown merged commit e4665f7 into codewhale-hq:main Oct 3, 2026
27 checks passed
Hmbown pushed a commit that referenced this pull request Oct 3, 2026
Retain the real main parent 296c05d
and the original PR merge/commit ancestry for #6819 (@Guan0923), #6829 and
#6831 (@Lstarsky0), and #6830 (@SparkofSpike). Resolve the 19 reviewed
counterparts to the exact V22 product tree, including all 156 original
translated values and the original localization regression test.

Exact V22 local checks: Rust 1351/0/0; localization 54/0/0; CI-policy Clippy PASS.
CLI: 3 actual processes / 6 checks / 6 local fixture requests / 0 paid calls; approval, resume, cancellation and EOF effects passed.
npm test + check:web: 1180 passed / 0 failed / 7 explicit skips (wrapper101, SDK19, host378, website682); strict TypeScript and production web passed.
No contributor harvesting, runtime default flip, install or release claim.

Signed-off-by: CodeWhale Bot <bot@codewhale.net>
Hmbown pushed a commit that referenced this pull request Oct 4, 2026
Windows Native hosts: on 02fcc3e the LPAC token check and the data-directory
share conflict were fixed, and the isolation probe ran inside the LPAC for the
first time. Node then aborted at startup (WSAStartup 10107): an LPAC cannot
open any registry key without the registryRead capability, and Winsock reads
its catalog from the registry. Native hosts now receive exactly that one
capability (derived by name), and the token check requires exactly it; it
grants no file, network or COM access, and socket creation stays denied,
which the probe checks. Bun failed opening the NUL device (EPERM) for
--config=NUL: the plain Bun host now reads an empty config from the granted
runtime-copy directory, and the Windows compiled host relies on
--no-compile-autoload-bunfig instead of baking --config=NUL. Unprotected file
ACL edits now pass an explicit-only DACL to SetSecurityInfo with
UNPROTECTED_DACL_SECURITY_INFORMATION so Windows re-derives inherited entries
from the parent (a file has no children to propagate to); the kernel write had
turned a file's inherited entries explicit. ACL pointer reads use checked
NonNull access (CodeQL rust/access-invalid-pointer on 02fcc3e).

Windows atomic writes: Fleet's WorkspaceFile/WindowsDirectory pins, new in
0.10.1, keep every ancestor directory of a Fleet file open with read-only
sharing. MoveFileExW (tempfile::persist) reopens the destination directory
with FILE_ADD_FILE, so while a Fleet ledger is alive (a whole `codewhale
fleet` run, or a Runtime API request) Codewhale's own atomic writes into the
workspace root or its ancestors failed with os error 32. That includes
file-tool edits; hosted Windows showed it as a bare 500 writing mcp.json. On
Windows, write_atomic now creates its temp with DELETE access and renames
through that handle with a bare file name (the rename Fleet publication
already uses), so the parent is never reopened and no pin is relaxed. Names
Windows would rewrite (trailing dot or space, device names) are refused
before any temp exists, and every failure path closes the delete-denying
handle before removing the temp. MCP config write errors now name the file.
A Windows test writes and replaces a file beside live Fleet pins and checks
refused names leave no temp behind.

Wire JSON: the extension-host `isJson` guard used Array.every and
Object.values, so it accepted sparse arrays, extra array properties,
accessors and symbol keys that serialize differently from what was checked
(Copilot review on #6815). It now accepts only dense arrays and plain objects
whose own keys are enumerable string data properties, the rule storage.ts
already enforced; storage's duplicate check is removed. A new test fails on
the old guard and passes on the new one. Bundles that include the guard were
rebuilt with the pinned esbuild; the harness builtin digest pin in tier.rs
moves with it.

Release notes: credits and entries for #6820, #6829, #6830 and #6831, which
landed in 0.10.1 without CHANGELOG coverage, and for #6827 (Windows node.exe
kills are held by the built-in safety floor even in Full Access), crediting
@jayanthvee in the CHANGELOG, docs/CONTRIBUTORS.md, website credits and
public-surface facts.

Fixes #6827

Source15 verification (3,792 rows; owning HEAD cf03333 and index 5983aa54
unchanged):
- npm test and npm run check:web: 1,185 passed / 0 failed / 7 explicit skips
  (the website credit-parity test caught the first draft missing the new
  reporter credit);
  extension-host suite 390 tests, 383 passed, 0 failed, 7 skipped.
- macOS, compiled host rebuilt from the new bundle: extension_host 177/0,
  plugins::install::dsh 28/0, mcp 407/0, utils 38/0, tools::file 190/0,
  runtime_store_convergence 14/0, builtin digest table 2/0.
- Windows: cargo check --target x86_64-pc-windows-gnu of codewhale-tui lib
  and tests clean under deny(warnings); write_atomic change reviewed
  adversarially (FIX_FIRST findings applied: early-error temp cleanup, single
  retry layer, rewritten-name refusal). The LPAC capability, Bun config and
  file-ACL changes are type-checked only; their runtime proof is this head's
  hosted Windows run.

Hosted 02fcc3e: Safety, Lint, npm wrapper smoke and Test (ubuntu-latest)
passed. Windows 17,952 passed / 91 failed: LPAC runtime startup (Node
WSAStartup, Bun NUL config), one file-ACL hygiene test, the mcp.json write
conflict and one DSH HTTP case behind the probe, all addressed above. CodeQL
flagged five pointer reads, addressed above. Not proven here: Windows runtime
of these repairs.
This is source integration, not release, deploy, provider or install
approval.

Evidence: artifacts/gpt61-0101-takeover-20261002/parent-source15-application,
parent-source15-local, parent-source15-npm-web-actual, windows-gnu-check.

Signed-off-by: CodeWhale Bot <bot@codewhale.net>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants