Skip to content

Update tree-sitter-highlight requirement from 0.24.7 to 0.25.2 - #3

Closed
dependabot[bot] wants to merge 1 commit into
masterfrom
dependabot/cargo/tree-sitter-highlight-0.25.2
Closed

Update tree-sitter-highlight requirement from 0.24.7 to 0.25.2#3
dependabot[bot] wants to merge 1 commit into
masterfrom
dependabot/cargo/tree-sitter-highlight-0.25.2

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Feb 24, 2025

Copy link
Copy Markdown
Contributor

Updates the requirements on tree-sitter-highlight to permit the latest version.

Release notes

Sourced from tree-sitter-highlight's releases.

v0.25.2

Bug Fixes

  • Fixed a crash that could occur when loading WASM-compiled languages that were generated with an earlier version of the Tree-sitter CLI (#4210).
  • Fixed incorrect tokenization when using WASM-compiled languages, if the language's external scanner did not assign to result_symbol (#4218)
  • Fixed an infinite loop that could occur if external scanners returned empty extra tokens (#4213)

Build

  • Decreased the rustc version required to build the tree-sitter-languages and tree-sitter crates (#4221)
Commits
  • 6e06187 0.25.2
  • 64665ec Decrease the MSRV for the tree-sitter-language crate (#4221) (#4222)
  • 1925a70 Reset result_symbol field of lexer in wasm memory in between invocations (#42...
  • 02625fc Ignore external tokens that are zero-length and extra (#4213) (#4216)
  • d799b78 Fix crash when loading languages w/ old ABI via wasm (#4210)
  • f5afe47 build: bump version to 0.25.1
  • f20d4b0 docs: correct build steps for WASM files
  • 05d443a style(rust): correct doc comments
  • eed662d fix(bindings): correct Zig bindings to expose a language function
  • 9ad096e fix(lib): prevent finished_tree assertion failure
  • Additional commits viewable in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


Dependabot commands and options

You can trigger Dependabot actions by commenting on this PR:

  • @dependabot rebase will rebase this PR
  • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
  • @dependabot merge will merge this PR after your CI passes on it
  • @dependabot squash and merge will squash and merge this PR after your CI passes on it
  • @dependabot cancel merge will cancel a previously requested merge and block automerging
  • @dependabot reopen will reopen this PR if it is closed
  • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
  • @dependabot show <dependency name> ignore conditions will show all of the ignore conditions of the specified dependency
  • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)

Updates the requirements on [tree-sitter-highlight](https://github.com/tree-sitter/tree-sitter) to permit the latest version.
- [Release notes](https://github.com/tree-sitter/tree-sitter/releases)
- [Commits](tree-sitter/tree-sitter@v0.24.7...v0.25.2)

---
updated-dependencies:
- dependency-name: tree-sitter-highlight
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot dependabot Bot added dependencies Pull requests that update a dependency file rust Pull requests that update Rust code labels Feb 24, 2025
@dependabot @github

dependabot Bot commented on behalf of github Mar 10, 2025

Copy link
Copy Markdown
Contributor Author

Superseded by #4.

@dependabot dependabot Bot closed this Mar 10, 2025
@dependabot
dependabot Bot deleted the dependabot/cargo/tree-sitter-highlight-0.25.2 branch March 10, 2025 01:53
sinelaw pushed a commit that referenced this pull request Apr 9, 2026
The test `test_named_color_swatch_uses_native_ansi_color` was flaky and
would occasionally time out at 180s with the selection stuck on
`syntax.variable` (deep past the intended `tab_active_fg` target).

Root cause: the navigation loops used bounded iteration caps
(`for _ in 0..50` / `for _ in 0..80`) and waited on "any screen change"
(`wait_until(|h| screen_to_string() != screen_before)`) between key
presses. Plugin hooks run on a separate thread and are fire-and-forget
(`thread.rs::run_hook`), so after `send_key(Down)` returns the plugin
may not yet have observed the key. Meanwhile any unrelated async work
(semantic-highlight timer, completion trigger, diagnostic pull, auto
file poll, etc.) can flip a screen cell between the keypress and the
actual selection update — satisfying the "screen changed" predicate
and letting the test race ahead of the plugin. Each racing iteration
sends another Down, so multiple key presses pile up and get processed
as a batch, skipping past `tab_active_fg` entirely. After 80 racing
iterations the selection ends up clamped at `syntax.variable` (the
final visible field), and the swatch-color wait then hangs forever on
a row that is no longer the intended target.

This also violated CONTRIBUTING.md #3 ("No timeouts or time-sensitive
tests. Use semantic waiting ... Wait indefinitely"): the 50/80 caps
are implicit iteration timeouts, and the "screen changed" condition
isn't a semantic state — it's an any-change heuristic.

Fix: replace both navigation loops with unbounded `loop {}` blocks
that wait on the *selected line's content* changing (the line
containing the `▸` marker). This is a true semantic condition —
the selected line's text only changes when the plugin has actually
processed the Down key and moved `state.selectedIndex`, so unrelated
async redraws can no longer let the test race ahead. Section
expansion uses the same pattern (wait until the selected line flips
from `▸> UI` to `▸▼ UI`), and the "move selection away" step before
checking the non-selected swatch also waits semantically.

Verified with 30 sequential runs + 8 concurrent runs + 3 full
theme_editor suite runs, all green.

https://claude.ai/code/session_01LWvqft9RenKnj9XnzQ7Vy4
sinelaw pushed a commit that referenced this pull request Apr 9, 2026
The test `test_named_color_swatch_uses_native_ansi_color` was flaky and
would occasionally time out at 180s with the selection stuck on
`syntax.variable` (deep past the intended `tab_active_fg` target).

Root cause: the navigation loops used bounded iteration caps
(`for _ in 0..50` / `for _ in 0..80`) and waited on "any screen change"
(`wait_until(|h| screen_to_string() != screen_before)`) between key
presses. Plugin hooks run on a separate thread and are fire-and-forget
(`thread.rs::run_hook`), so after `send_key(Down)` returns the plugin
may not yet have observed the key. Meanwhile any unrelated async work
(semantic-highlight timer, completion trigger, diagnostic pull, auto
file poll, etc.) can flip a screen cell between the keypress and the
actual selection update — satisfying the "screen changed" predicate
and letting the test race ahead of the plugin. Each racing iteration
sends another Down, so multiple key presses pile up and get processed
as a batch, skipping past `tab_active_fg` entirely. After 80 racing
iterations the selection ends up clamped at `syntax.variable` (the
final visible field), and the swatch-color wait then hangs forever on
a row that is no longer the intended target.

This also violated CONTRIBUTING.md #3 ("No timeouts or time-sensitive
tests. Use semantic waiting ... Wait indefinitely"): the 50/80 caps
are implicit iteration timeouts, and the "screen changed" condition
isn't a semantic state — it's an any-change heuristic.

Fix: replace both navigation loops with unbounded `loop {}` blocks
that wait on the *selected line's content* changing (the line
containing the `▸` marker). This is a true semantic condition —
the selected line's text only changes when the plugin has actually
processed the Down key and moved `state.selectedIndex`, so unrelated
async redraws can no longer let the test race ahead. Section
expansion uses the same pattern (wait until the selected line flips
from `▸> UI` to `▸▼ UI`), and the "move selection away" step before
checking the non-selected swatch also waits semantically.

Verified with 30 sequential runs + 8 concurrent runs + 3 full
theme_editor suite runs, all green.

https://claude.ai/code/session_01LWvqft9RenKnj9XnzQ7Vy4
sinelaw pushed a commit that referenced this pull request Apr 9, 2026
The test `test_named_color_swatch_uses_native_ansi_color` was flaky and
would occasionally time out at 180s with the selection stuck on
`syntax.variable` (deep past the intended `tab_active_fg` target).

Root cause: the navigation loops used bounded iteration caps
(`for _ in 0..50` / `for _ in 0..80`) and waited on "any screen change"
(`wait_until(|h| screen_to_string() != screen_before)`) between key
presses. Plugin hooks run on a separate thread and are fire-and-forget
(`thread.rs::run_hook`), so after `send_key(Down)` returns the plugin
may not yet have observed the key. Meanwhile any unrelated async work
(semantic-highlight timer, completion trigger, diagnostic pull, auto
file poll, etc.) can flip a screen cell between the keypress and the
actual selection update — satisfying the "screen changed" predicate
and letting the test race ahead of the plugin. Each racing iteration
sends another Down, so multiple key presses pile up and get processed
as a batch, skipping past `tab_active_fg` entirely. After 80 racing
iterations the selection ends up clamped at `syntax.variable` (the
final visible field), and the swatch-color wait then hangs forever on
a row that is no longer the intended target.

This also violated CONTRIBUTING.md #3 ("No timeouts or time-sensitive
tests. Use semantic waiting ... Wait indefinitely"): the 50/80 caps
are implicit iteration timeouts, and the "screen changed" condition
isn't a semantic state — it's an any-change heuristic.

Fix: replace both navigation loops with unbounded `loop {}` blocks
that wait on the *selected line's content* changing (the line
containing the `▸` marker). This is a true semantic condition —
the selected line's text only changes when the plugin has actually
processed the Down key and moved `state.selectedIndex`, so unrelated
async redraws can no longer let the test race ahead. Section
expansion uses the same pattern (wait until the selected line flips
from `▸> UI` to `▸▼ UI`), and the "move selection away" step before
checking the non-selected swatch also waits semantically.

Verified with 30 sequential runs + 8 concurrent runs + 3 full
theme_editor suite runs, all green.

https://claude.ai/code/session_01LWvqft9RenKnj9XnzQ7Vy4
sinelaw pushed a commit that referenced this pull request Apr 9, 2026
The test `test_named_color_swatch_uses_native_ansi_color` was flaky and
would occasionally time out at 180s with the selection stuck on
`syntax.variable` (deep past the intended `tab_active_fg` target).

Root cause: the navigation loops used bounded iteration caps
(`for _ in 0..50` / `for _ in 0..80`) and waited on "any screen change"
(`wait_until(|h| screen_to_string() != screen_before)`) between key
presses. Plugin hooks run on a separate thread and are fire-and-forget
(`thread.rs::run_hook`), so after `send_key(Down)` returns the plugin
may not yet have observed the key. Meanwhile any unrelated async work
(semantic-highlight timer, completion trigger, diagnostic pull, auto
file poll, etc.) can flip a screen cell between the keypress and the
actual selection update — satisfying the "screen changed" predicate
and letting the test race ahead of the plugin. Each racing iteration
sends another Down, so multiple key presses pile up and get processed
as a batch, skipping past `tab_active_fg` entirely. After 80 racing
iterations the selection ends up clamped at `syntax.variable` (the
final visible field), and the swatch-color wait then hangs forever on
a row that is no longer the intended target.

This also violated CONTRIBUTING.md #3 ("No timeouts or time-sensitive
tests. Use semantic waiting ... Wait indefinitely"): the 50/80 caps
are implicit iteration timeouts, and the "screen changed" condition
isn't a semantic state — it's an any-change heuristic.

Fix: replace both navigation loops with unbounded `loop {}` blocks
that wait on the *selected line's content* changing (the line
containing the `▸` marker). This is a true semantic condition —
the selected line's text only changes when the plugin has actually
processed the Down key and moved `state.selectedIndex`, so unrelated
async redraws can no longer let the test race ahead. Section
expansion uses the same pattern (wait until the selected line flips
from `▸> UI` to `▸▼ UI`), and the "move selection away" step before
checking the non-selected swatch also waits semantically.

Verified with 30 sequential runs + 8 concurrent runs + 3 full
theme_editor suite runs, all green.

https://claude.ai/code/session_01LWvqft9RenKnj9XnzQ7Vy4
sinelaw pushed a commit that referenced this pull request Apr 22, 2026
test_review_diff_n_auto_expands_collapsed_file flaked on Windows CI
because it called `render()` once after pressing `n` and then asserted
on screen content. The `n` key maps to the plugin's `review_next_hunk`
handler, which calls `updateMagitDisplay()` to rebuild the diff buffer
asynchronously — a single render tick on slower Windows runners does
not always flush that rebuild, so the screen still showed the
collapsed file headers without any hunk content when the assertion
ran.

Per CONTRIBUTING.md guideline #3 ("No timeouts or time-sensitive
tests: Use semantic waiting instead of fixed timers"), replace the
render+immediate-assert pattern with `wait_until` so the test waits
for the expanded hunk content to actually appear on screen.
sinelaw pushed a commit that referenced this pull request Apr 26, 2026
`scroll_single_long_line_perf.rs` asserted wall-clock duration against a
fixed 12s budget — exactly the kind of "time-sensitive" test
CONTRIBUTING.md rule #3 forbids:

  > No timeouts or time-sensitive tests: use "semantic waiting" instead
  > of fixed timers to ensure test stability. Wait indefinitely, don't
  > put timeouts inside tests (cargo nextest will timeout externally).

The test was added on this branch (commit 742592d) as a guard against
an O(n²) regression that pre-dated the LineWrapCache + VisualRowIndex
refactor.  With the cache + tier-2 index in place, the structural
design prevents `apply_wrapping_transform` from running uncached on
every scroll tick — there's no path that can re-introduce that
specific O(n²) shape.  The wall-clock assertion has been racing CI
hardware, and producing flaky failures (CI: 17.6s vs 12s budget; same
test passes locally at ~11s) without the failure pointing at any real
regression.

Also picks up trivial `cargo fmt` adjustments to the four files
touched by the recent VisualRowIndex work.
sinelaw pushed a commit that referenced this pull request Apr 26, 2026
`scroll_single_long_line_perf.rs` asserted wall-clock duration against a
fixed 12s budget — exactly the kind of "time-sensitive" test
CONTRIBUTING.md rule #3 forbids:

  > No timeouts or time-sensitive tests: use "semantic waiting" instead
  > of fixed timers to ensure test stability. Wait indefinitely, don't
  > put timeouts inside tests (cargo nextest will timeout externally).

The test was added on this branch (commit 742592d) as a guard against
an O(n²) regression that pre-dated the LineWrapCache + VisualRowIndex
refactor.  With the cache + tier-2 index in place, the structural
design prevents `apply_wrapping_transform` from running uncached on
every scroll tick — there's no path that can re-introduce that
specific O(n²) shape.  The wall-clock assertion has been racing CI
hardware, and producing flaky failures (CI: 17.6s vs 12s budget; same
test passes locally at ~11s) without the failure pointing at any real
regression.

Also picks up trivial `cargo fmt` adjustments to the four files
touched by the recent VisualRowIndex work.
sinelaw pushed a commit that referenced this pull request Apr 26, 2026
Bug #3 from DEVCONTAINER_USABILITY_TEST_2026-04-26.md (L170,
Critical): when the editor restarts after a Rebuild against a
malformed `devcontainer.json` (or any path that re-runs plugin
init with broken JSON), the plugin's old `if (findConfig()) {
registerCommands(); ... }` would skip the entire `else` branch.
The user lost every `Dev Container:` command, including
`Open Config` — the only in-editor route back to fix the file.
Recovery required killing and restarting the editor.

Fix in `plugins/devcontainer.ts`:
* `tryParse` now sets `configPath` even on parse failure so the
  recovery `Open Config` knows where the broken file lives.
* New `registerRecoveryCommands()` registers a tiny set
  (`open_config`, `show_build_logs`) and unregisters the
  full-config commands. Used in the broken-config branch of
  plugin init.
* The `findConfig() === false` branch now calls both
  `showParseErrorIfAny()` (Bug #2) and `registerRecoveryCommands()`,
  so the user sees the parse error AND has a navigable command.

Test: `broken_devcontainer_json_keeps_recovery_commands_registered`.
Asserts `%cmd.open_config` + `%cmd.show_build_logs` survive a
plugin init against unparseable JSON, while `%cmd.rebuild` /
`%cmd.attach` (which would no-op without a parsed config) are
correctly absent.

Bug #2's regression test (`broken_devcontainer_json_surfaces_parse_
error_in_status_bar`) still passes — the status emit and the
recovery registration coexist.
sinelaw pushed a commit that referenced this pull request Apr 26, 2026
Bug #3 from DEVCONTAINER_USABILITY_TEST_2026-04-26.md (L170,
Critical): when the editor restarts after a Rebuild against a
malformed `devcontainer.json` (or any path that re-runs plugin
init with broken JSON), the plugin's old `if (findConfig()) {
registerCommands(); ... }` would skip the entire `else` branch.
The user lost every `Dev Container:` command, including
`Open Config` — the only in-editor route back to fix the file.
Recovery required killing and restarting the editor.

Fix in `plugins/devcontainer.ts`:
* `tryParse` now sets `configPath` even on parse failure so the
  recovery `Open Config` knows where the broken file lives.
* New `registerRecoveryCommands()` registers a tiny set
  (`open_config`, `show_build_logs`) and unregisters the
  full-config commands. Used in the broken-config branch of
  plugin init.
* The `findConfig() === false` branch now calls both
  `showParseErrorIfAny()` (Bug #2) and `registerRecoveryCommands()`,
  so the user sees the parse error AND has a navigable command.

Test: `broken_devcontainer_json_keeps_recovery_commands_registered`.
Asserts `%cmd.open_config` + `%cmd.show_build_logs` survive a
plugin init against unparseable JSON, while `%cmd.rebuild` /
`%cmd.attach` (which would no-op without a parsed config) are
correctly absent.

Bug #2's regression test (`broken_devcontainer_json_surfaces_parse_
error_in_status_bar`) still passes — the status emit and the
recovery registration coexist.
sinelaw pushed a commit that referenced this pull request Apr 27, 2026
Bug #3 from DEVCONTAINER_USABILITY_TEST_2026-04-26.md (L170,
Critical): when the editor restarts after a Rebuild against a
malformed `devcontainer.json` (or any path that re-runs plugin
init with broken JSON), the plugin's old `if (findConfig()) {
registerCommands(); ... }` would skip the entire `else` branch.
The user lost every `Dev Container:` command, including
`Open Config` — the only in-editor route back to fix the file.
Recovery required killing and restarting the editor.

Fix in `plugins/devcontainer.ts`:
* `tryParse` now sets `configPath` even on parse failure so the
  recovery `Open Config` knows where the broken file lives.
* New `registerRecoveryCommands()` registers a tiny set
  (`open_config`, `show_build_logs`) and unregisters the
  full-config commands. Used in the broken-config branch of
  plugin init.
* The `findConfig() === false` branch now calls both
  `showParseErrorIfAny()` (Bug #2) and `registerRecoveryCommands()`,
  so the user sees the parse error AND has a navigable command.

Test: `broken_devcontainer_json_keeps_recovery_commands_registered`.
Asserts `%cmd.open_config` + `%cmd.show_build_logs` survive a
plugin init against unparseable JSON, while `%cmd.rebuild` /
`%cmd.attach` (which would no-op without a parsed config) are
correctly absent.

Bug #2's regression test (`broken_devcontainer_json_surfaces_parse_
error_in_status_bar`) still passes — the status emit and the
recovery registration coexist.
sinelaw pushed a commit that referenced this pull request Apr 27, 2026
…meout

Two unrelated CI flakes, both rooted in time-sensitive test
infrastructure (CONTRIBUTING.md rule #3 — "Wait indefinitely,
don't put timeouts inside tests").

## `test_multiple_reconnections` — bumped TEST_TIMEOUT 2s → 30s

Symptom (slow CI): "Round 2: request should succeed: Err(Timeout)".

The channel's `set_request_timeout` is a SUT parameter — every
request the test issues through `request_blocking` is bounded by it,
including the happy-path "should succeed" requests.  At 2s, a load
spike that delays the python agent's startup or response by even 1s
trips the assertion before the agent can respond.

Bumped to 30s — "essentially infinity" for sub-millisecond agent
responses on a healthy runner, while still letting the explicit
intentional-timeout assertions (`silent_agent` cases) pay at most
30s once.  Tests with multiple back-to-back intentional timeouts
(`test_multiple_reconnections`'s 3 rounds) run on the order of
`TEST_TIMEOUT × N` wall-clock; that's bounded by nextest's external
per-test timeout (default 180s) so we don't need an internal cap.

## `compose_default_width_table_scrollbar_drag` — added buffer-scrub warmup

Symptom (parallel CI): "default-width/table/scrollbar-drag] 2 of 3
(width, height) combo(s) failed" at w=60 / w=100, with viewport
stuck mid-table.

Root cause: the markdown_compose plugin processes `lines_changed`
**reactively** for the currently-visible window only.  Off-screen
lines have no plugin soft breaks / virtual borders in
`state.soft_breaks` / `state.virtual_texts` until the user scrolls
that region into view.  Scroll math
(`scrollbar_math::ensure_index` → `VisualRowIndex`) reads from those
structures, so an unprocessed off-screen region undercounts
`max_scroll_row` and scrollbar drag stops short of the buffer's
tail.  Mouse wheel and PageDown have a per-step
`apply_visual_scroll_limit` re-clamp that masks the same
under-count for those mechanisms; scrollbar drag has no such
re-clamp.

The previous setup waited for `wait_until_stable` on the
**visible-region** condition only (no `**` markers in screen
content).  Under parallel test load, this returned before the
plugin had processed off-screen lines.

Fix: setup now does a Ctrl+End → wait_until_stable → Ctrl+Home →
wait_until_stable buffer scrub.  Each jump brings a different
window into view, so the plugin processes both ends of the buffer
(and any markdown structure within `viewport_height` of either
end).  The fixtures have the marker line near EOF, so a single
Ctrl+End brings it through the visible window once and the plugin
populates `state.soft_breaks` / `state.virtual_texts` for the
table region.  Subsequent scroll math has accurate counts.

Both waits use semantic conditions (CONTRIBUTING.md rule #3) — no
fixed timers, no time-sensitive assertions.  The warmup itself is
bounded only by nextest's external timeout.

Note: this is a test-side fix.  In production, a user opening a
large markdown file, toggling compose, and immediately dragging
the scrollbar to the bottom will see the same undershoot until
they've scrolled around enough for the plugin to process every
line.  A proper production fix (eager whole-buffer
`lines_changed` on compose toggle, gated to compose-mode buffers
to avoid the `large.rs` memory regression) was drafted on this
branch's earlier history but rolled back along with the perf
rebase upstream; reintroducing it cleanly is follow-up work.

## Verification

* `markdown_compose_scroll_reach::*`: 20 passed, 0 failed,
  3 ignored — twice in a row in parallel mode.
* `remote_channel_timeout_tests::test_multiple_reconnections`:
  passes in 90s wall (3 rounds × 30s timeout assertion = 90s,
  well within nextest's 180s budget).

https://claude.ai/code/session_01CmCEFyuNawokswQUTQzAJa
sinelaw pushed a commit that referenced this pull request Apr 27, 2026
… git_log_split_tab_focus

Two unrelated test stability fixes.

## R1 lifecycle reproducer — barrier instead of wall-clock

`devcontainer_spec_conformance::lifecycle_object_form_must_run_in_parallel`
asserted parallelism via wall-clock measurement (sequential lower
bound 1.3s, parallel upper bound ~0.5s, threshold 1.1s).  Under CI
load the parallel path could exceed 1.1s due to docker-exec
overhead, surfacing as

    R1 (failing on master): postCreateCommand object form should
    run entries in parallel. Wall clock = 1.117s > 1.1s

This violates CONTRIBUTING.md rule #3 ("No timeouts or
time-sensitive tests").  Replaced with a 3-entry **barrier**:

  1. each entry touches its own `start_X` sentinel
  2. each entry waits up to 3s for the OTHER two `start_*`
     sentinels to exist
  3. only after both observed, each entry touches its `done_X`

If the plugin runs entries in parallel, all 3 starts appear within
ms, every entry observes the others and touches its done.  If the
plugin runs entries sequentially the first entry's barrier wait
can never succeed (the next entry can't start until the first
finishes — chicken-and-egg), so it exhausts its retry budget and
exits 1 without touching `done_a`.  Test asserts all three
`done_X` exist; in the sequential failure mode it fails fast with
"entry `b` never satisfied the barrier — implies sequential
execution" instead of a wall-clock comparison.

Same intent as before — fails on master where the plugin runs
`postCreateCommand` entries in a sequential `for` loop, will pass
once the plugin uses `Promise.all`.  The new design has no wall-
clock measurement, no fixed timer in the test (only a script-side
retry loop), and is bounded only by nextest's external per-test
timeout.

## git_log_split_tab_focus — diagnostic instrumentation

`clicking_group_tab_activates_group_in_the_clicked_split` has been
seen flaking with a 180s nextest timeout in CI.  Added the
standard pair used by other tests in this directory:

    init_tracing_from_env();
    fresh::services::signal_handler::install_signal_handlers();

so the next CI hang dumps tracing breadcrumbs (RUST_LOG=info to
expand) plus a backtrace on SIGABRT/SIGSEGV instead of producing
a bare timeout line.  This is debugging instrumentation only — it
does not attempt to fix the underlying flake (per request).

https://claude.ai/code/session_01CmCEFyuNawokswQUTQzAJa
sinelaw pushed a commit that referenced this pull request Apr 27, 2026
…meout

Two unrelated CI flakes, both rooted in time-sensitive test
infrastructure (CONTRIBUTING.md rule #3 — "Wait indefinitely,
don't put timeouts inside tests").

## `test_multiple_reconnections` — bumped TEST_TIMEOUT 2s → 30s

Symptom (slow CI): "Round 2: request should succeed: Err(Timeout)".

The channel's `set_request_timeout` is a SUT parameter — every
request the test issues through `request_blocking` is bounded by it,
including the happy-path "should succeed" requests.  At 2s, a load
spike that delays the python agent's startup or response by even 1s
trips the assertion before the agent can respond.

Bumped to 30s — "essentially infinity" for sub-millisecond agent
responses on a healthy runner, while still letting the explicit
intentional-timeout assertions (`silent_agent` cases) pay at most
30s once.  Tests with multiple back-to-back intentional timeouts
(`test_multiple_reconnections`'s 3 rounds) run on the order of
`TEST_TIMEOUT × N` wall-clock; that's bounded by nextest's external
per-test timeout (default 180s) so we don't need an internal cap.

## `compose_default_width_table_scrollbar_drag` — added buffer-scrub warmup

Symptom (parallel CI): "default-width/table/scrollbar-drag] 2 of 3
(width, height) combo(s) failed" at w=60 / w=100, with viewport
stuck mid-table.

Root cause: the markdown_compose plugin processes `lines_changed`
**reactively** for the currently-visible window only.  Off-screen
lines have no plugin soft breaks / virtual borders in
`state.soft_breaks` / `state.virtual_texts` until the user scrolls
that region into view.  Scroll math
(`scrollbar_math::ensure_index` → `VisualRowIndex`) reads from those
structures, so an unprocessed off-screen region undercounts
`max_scroll_row` and scrollbar drag stops short of the buffer's
tail.  Mouse wheel and PageDown have a per-step
`apply_visual_scroll_limit` re-clamp that masks the same
under-count for those mechanisms; scrollbar drag has no such
re-clamp.

The previous setup waited for `wait_until_stable` on the
**visible-region** condition only (no `**` markers in screen
content).  Under parallel test load, this returned before the
plugin had processed off-screen lines.

Fix: setup now does a Ctrl+End → wait_until_stable → Ctrl+Home →
wait_until_stable buffer scrub.  Each jump brings a different
window into view, so the plugin processes both ends of the buffer
(and any markdown structure within `viewport_height` of either
end).  The fixtures have the marker line near EOF, so a single
Ctrl+End brings it through the visible window once and the plugin
populates `state.soft_breaks` / `state.virtual_texts` for the
table region.  Subsequent scroll math has accurate counts.

Both waits use semantic conditions (CONTRIBUTING.md rule #3) — no
fixed timers, no time-sensitive assertions.  The warmup itself is
bounded only by nextest's external timeout.

Note: this is a test-side fix.  In production, a user opening a
large markdown file, toggling compose, and immediately dragging
the scrollbar to the bottom will see the same undershoot until
they've scrolled around enough for the plugin to process every
line.  A proper production fix (eager whole-buffer
`lines_changed` on compose toggle, gated to compose-mode buffers
to avoid the `large.rs` memory regression) was drafted on this
branch's earlier history but rolled back along with the perf
rebase upstream; reintroducing it cleanly is follow-up work.

## Verification

* `markdown_compose_scroll_reach::*`: 20 passed, 0 failed,
  3 ignored — twice in a row in parallel mode.
* `remote_channel_timeout_tests::test_multiple_reconnections`:
  passes in 90s wall (3 rounds × 30s timeout assertion = 90s,
  well within nextest's 180s budget).

https://claude.ai/code/session_01CmCEFyuNawokswQUTQzAJa
sinelaw pushed a commit that referenced this pull request Apr 27, 2026
… git_log_split_tab_focus

Two unrelated test stability fixes.

## R1 lifecycle reproducer — barrier instead of wall-clock

`devcontainer_spec_conformance::lifecycle_object_form_must_run_in_parallel`
asserted parallelism via wall-clock measurement (sequential lower
bound 1.3s, parallel upper bound ~0.5s, threshold 1.1s).  Under CI
load the parallel path could exceed 1.1s due to docker-exec
overhead, surfacing as

    R1 (failing on master): postCreateCommand object form should
    run entries in parallel. Wall clock = 1.117s > 1.1s

This violates CONTRIBUTING.md rule #3 ("No timeouts or
time-sensitive tests").  Replaced with a 3-entry **barrier**:

  1. each entry touches its own `start_X` sentinel
  2. each entry waits up to 3s for the OTHER two `start_*`
     sentinels to exist
  3. only after both observed, each entry touches its `done_X`

If the plugin runs entries in parallel, all 3 starts appear within
ms, every entry observes the others and touches its done.  If the
plugin runs entries sequentially the first entry's barrier wait
can never succeed (the next entry can't start until the first
finishes — chicken-and-egg), so it exhausts its retry budget and
exits 1 without touching `done_a`.  Test asserts all three
`done_X` exist; in the sequential failure mode it fails fast with
"entry `b` never satisfied the barrier — implies sequential
execution" instead of a wall-clock comparison.

Same intent as before — fails on master where the plugin runs
`postCreateCommand` entries in a sequential `for` loop, will pass
once the plugin uses `Promise.all`.  The new design has no wall-
clock measurement, no fixed timer in the test (only a script-side
retry loop), and is bounded only by nextest's external per-test
timeout.

## git_log_split_tab_focus — diagnostic instrumentation

`clicking_group_tab_activates_group_in_the_clicked_split` has been
seen flaking with a 180s nextest timeout in CI.  Added the
standard pair used by other tests in this directory:

    init_tracing_from_env();
    fresh::services::signal_handler::install_signal_handlers();

so the next CI hang dumps tracing breadcrumbs (RUST_LOG=info to
expand) plus a backtrace on SIGABRT/SIGSEGV instead of producing
a bare timeout line.  This is debugging instrumentation only — it
does not attempt to fix the underlying flake (per request).

https://claude.ai/code/session_01CmCEFyuNawokswQUTQzAJa
sinelaw pushed a commit that referenced this pull request Apr 27, 2026
…meout

Two unrelated CI flakes, both rooted in time-sensitive test
infrastructure (CONTRIBUTING.md rule #3 — "Wait indefinitely,
don't put timeouts inside tests").

## `test_multiple_reconnections` — bumped TEST_TIMEOUT 2s → 30s

Symptom (slow CI): "Round 2: request should succeed: Err(Timeout)".

The channel's `set_request_timeout` is a SUT parameter — every
request the test issues through `request_blocking` is bounded by it,
including the happy-path "should succeed" requests.  At 2s, a load
spike that delays the python agent's startup or response by even 1s
trips the assertion before the agent can respond.

Bumped to 30s — "essentially infinity" for sub-millisecond agent
responses on a healthy runner, while still letting the explicit
intentional-timeout assertions (`silent_agent` cases) pay at most
30s once.  Tests with multiple back-to-back intentional timeouts
(`test_multiple_reconnections`'s 3 rounds) run on the order of
`TEST_TIMEOUT × N` wall-clock; that's bounded by nextest's external
per-test timeout (default 180s) so we don't need an internal cap.

## `compose_default_width_table_scrollbar_drag` — added buffer-scrub warmup

Symptom (parallel CI): "default-width/table/scrollbar-drag] 2 of 3
(width, height) combo(s) failed" at w=60 / w=100, with viewport
stuck mid-table.

Root cause: the markdown_compose plugin processes `lines_changed`
**reactively** for the currently-visible window only.  Off-screen
lines have no plugin soft breaks / virtual borders in
`state.soft_breaks` / `state.virtual_texts` until the user scrolls
that region into view.  Scroll math
(`scrollbar_math::ensure_index` → `VisualRowIndex`) reads from those
structures, so an unprocessed off-screen region undercounts
`max_scroll_row` and scrollbar drag stops short of the buffer's
tail.  Mouse wheel and PageDown have a per-step
`apply_visual_scroll_limit` re-clamp that masks the same
under-count for those mechanisms; scrollbar drag has no such
re-clamp.

The previous setup waited for `wait_until_stable` on the
**visible-region** condition only (no `**` markers in screen
content).  Under parallel test load, this returned before the
plugin had processed off-screen lines.

Fix: setup now does a Ctrl+End → wait_until_stable → Ctrl+Home →
wait_until_stable buffer scrub.  Each jump brings a different
window into view, so the plugin processes both ends of the buffer
(and any markdown structure within `viewport_height` of either
end).  The fixtures have the marker line near EOF, so a single
Ctrl+End brings it through the visible window once and the plugin
populates `state.soft_breaks` / `state.virtual_texts` for the
table region.  Subsequent scroll math has accurate counts.

Both waits use semantic conditions (CONTRIBUTING.md rule #3) — no
fixed timers, no time-sensitive assertions.  The warmup itself is
bounded only by nextest's external timeout.

Note: this is a test-side fix.  In production, a user opening a
large markdown file, toggling compose, and immediately dragging
the scrollbar to the bottom will see the same undershoot until
they've scrolled around enough for the plugin to process every
line.  A proper production fix (eager whole-buffer
`lines_changed` on compose toggle, gated to compose-mode buffers
to avoid the `large.rs` memory regression) was drafted on this
branch's earlier history but rolled back along with the perf
rebase upstream; reintroducing it cleanly is follow-up work.

## Verification

* `markdown_compose_scroll_reach::*`: 20 passed, 0 failed,
  3 ignored — twice in a row in parallel mode.
* `remote_channel_timeout_tests::test_multiple_reconnections`:
  passes in 90s wall (3 rounds × 30s timeout assertion = 90s,
  well within nextest's 180s budget).

https://claude.ai/code/session_01CmCEFyuNawokswQUTQzAJa
sinelaw pushed a commit that referenced this pull request Apr 27, 2026
… git_log_split_tab_focus

Two unrelated test stability fixes.

## R1 lifecycle reproducer — barrier instead of wall-clock

`devcontainer_spec_conformance::lifecycle_object_form_must_run_in_parallel`
asserted parallelism via wall-clock measurement (sequential lower
bound 1.3s, parallel upper bound ~0.5s, threshold 1.1s).  Under CI
load the parallel path could exceed 1.1s due to docker-exec
overhead, surfacing as

    R1 (failing on master): postCreateCommand object form should
    run entries in parallel. Wall clock = 1.117s > 1.1s

This violates CONTRIBUTING.md rule #3 ("No timeouts or
time-sensitive tests").  Replaced with a 3-entry **barrier**:

  1. each entry touches its own `start_X` sentinel
  2. each entry waits up to 3s for the OTHER two `start_*`
     sentinels to exist
  3. only after both observed, each entry touches its `done_X`

If the plugin runs entries in parallel, all 3 starts appear within
ms, every entry observes the others and touches its done.  If the
plugin runs entries sequentially the first entry's barrier wait
can never succeed (the next entry can't start until the first
finishes — chicken-and-egg), so it exhausts its retry budget and
exits 1 without touching `done_a`.  Test asserts all three
`done_X` exist; in the sequential failure mode it fails fast with
"entry `b` never satisfied the barrier — implies sequential
execution" instead of a wall-clock comparison.

Same intent as before — fails on master where the plugin runs
`postCreateCommand` entries in a sequential `for` loop, will pass
once the plugin uses `Promise.all`.  The new design has no wall-
clock measurement, no fixed timer in the test (only a script-side
retry loop), and is bounded only by nextest's external per-test
timeout.

## git_log_split_tab_focus — diagnostic instrumentation

`clicking_group_tab_activates_group_in_the_clicked_split` has been
seen flaking with a 180s nextest timeout in CI.  Added the
standard pair used by other tests in this directory:

    init_tracing_from_env();
    fresh::services::signal_handler::install_signal_handlers();

so the next CI hang dumps tracing breadcrumbs (RUST_LOG=info to
expand) plus a backtrace on SIGABRT/SIGSEGV instead of producing
a bare timeout line.  This is debugging instrumentation only — it
does not attempt to fix the underlying flake (per request).

https://claude.ai/code/session_01CmCEFyuNawokswQUTQzAJa
sinelaw pushed a commit that referenced this pull request Apr 28, 2026
… don't race

The flash plugin's main loop updated `state.pattern` and the
`Flash[<pattern>]` status banner inside the keypress handler, BEFORE
`continue`ing back to the top of the loop where the next iteration
recomputes labels and redraws conceals.  The banner therefore reached
the screen ahead of the matching conceals — a render tick in that
window showed banner=N with conceals from iteration N-1.

E2E tests (`type_pattern` in tests/e2e/flash.rs) treat the banner as a
synchronization barrier: they `wait_until` the screen contains
`Flash[<pattern>]` before asserting on rendered conceals.  Under load
on slower / Windows CI the renderer would tick between the banner
update and the redraw, the test would observe banner="PID" with
conceals still anchored to "PI", and `flash_label_does_not_eat_space_after_match`
would fail with rendered output like `PIa file lockup` (label `a` on
the `D` cell, where it would land for pattern "PI").

Fix: call `setStatusForPattern()` AFTER `redraw()` within the same loop
iteration, and drop the eager calls inside the keypress handler.  Now
any observer that sees `Flash[<pattern>]` is guaranteed to also see the
conceals/labels for that pattern.  Per CONTRIBUTING rule #3 the test
keeps semantic waiting (no timeouts); the fix makes its existing wait
condition correct rather than introducing one.
sinelaw pushed a commit that referenced this pull request Apr 30, 2026
Two paper cuts surfaced after smoke-testing 9e302d6:

1. Quickfix and other dock-routed virtual buffers showed up as a tab in
   *both* the editor split and the dock leaf. Cause: create_virtual_buffer
   tabs the new buffer into the active split before the dispatcher
   re-parents it to the dock. Fix: capture the source split before
   create_virtual_buffer, and call source.remove_buffer(buffer_id) after
   set_pane_buffer(dock_leaf, ...) — same pattern the existing
   dispatcher uses on the cold-start branch (#3 in the comments at
   plugin_dispatch.rs:2090). Applied in both
   handle_create_virtual_buffer_in_split (utility-dock fast path) and
   Editor::install_quickfix_in_dock.

2. Pressing Tab inside the floating overlay clobbered the search query
   with the suggestion's `value` field — which the Finder library uses
   as an opaque index ("0", "1", …), so the input visibly became "0".
   Fix: in view/prompt_input.rs, short-circuit the Tab handler when
   prompt.overlay is true (arrows already navigate suggestions, so Tab
   has no other useful job in overlay mode).

Verified in tmux: Alt+/ → type "split_active" → Tab leaves the input
unchanged ("split_active", 1/31 still selected); Alt+Q exports to
the dock and the top tab bar shows only "README.md" while the dock
holds only "*Quickfix*".

https://claude.ai/code/session_01FJMAjx2SQXjqsrmW1ojCXr
sinelaw pushed a commit that referenced this pull request Apr 30, 2026
…md rule #3

The file shared a single 30s `TEST_TIMEOUT` between two assertion
classes that have opposite contracts:
  - intentional-timeout assertions need a finite timeout (that's the
    behavior under test);
  - happy-path "should succeed" assertions must, per rule #3, wait
    indefinitely so that CI load spikes can't flip them into spurious
    `Err(Timeout)`.

The flake report on `test_multiple_reconnections` ("Round 3: request
should succeed: Err(Timeout)") was the predictable failure mode of
that conflict: a slow scheduler push during a should-succeed call
exceeded the shared 30s ceiling.

Fix: arm the channel's per-request timeout per call via two helpers
— `arm_intentional_timeout` (2s) before deliberate-timeout
assertions, `arm_happy_path` (1h, "essentially infinite") before any
should-succeed assertion. nextest's external per-test cap still
catches genuine hangs.

5 consecutive local runs of test_multiple_reconnections complete
deterministically in 6.09s (down from a 30+ second wall-clock
budget that was tripping at the 90s mark on slow runners).

https://claude.ai/code/session_01H9y9oj4ZZqWnDURtJfk1u7
sinelaw pushed a commit that referenced this pull request Apr 30, 2026
…md rule #3

The file shared a single 30s `TEST_TIMEOUT` between two assertion
classes that have opposite contracts:
  - intentional-timeout assertions need a finite timeout (that's the
    behavior under test);
  - happy-path "should succeed" assertions must, per rule #3, wait
    indefinitely so that CI load spikes can't flip them into spurious
    `Err(Timeout)`.

The flake report on `test_multiple_reconnections` ("Round 3: request
should succeed: Err(Timeout)") was the predictable failure mode of
that conflict: a slow scheduler push during a should-succeed call
exceeded the shared 30s ceiling.

Fix: arm the channel's per-request timeout per call via two helpers
— `arm_intentional_timeout` (2s) before deliberate-timeout
assertions, `arm_happy_path` (1h, "essentially infinite") before any
should-succeed assertion. nextest's external per-test cap still
catches genuine hangs.

5 consecutive local runs of test_multiple_reconnections complete
deterministically in 6.09s (down from a 30+ second wall-clock
budget that was tripping at the 90s mark on slow runners).

https://claude.ai/code/session_01H9y9oj4ZZqWnDURtJfk1u7
sinelaw pushed a commit that referenced this pull request Apr 30, 2026
Two paper cuts surfaced after smoke-testing 9e302d6:

1. Quickfix and other dock-routed virtual buffers showed up as a tab in
   *both* the editor split and the dock leaf. Cause: create_virtual_buffer
   tabs the new buffer into the active split before the dispatcher
   re-parents it to the dock. Fix: capture the source split before
   create_virtual_buffer, and call source.remove_buffer(buffer_id) after
   set_pane_buffer(dock_leaf, ...) — same pattern the existing
   dispatcher uses on the cold-start branch (#3 in the comments at
   plugin_dispatch.rs:2090). Applied in both
   handle_create_virtual_buffer_in_split (utility-dock fast path) and
   Editor::install_quickfix_in_dock.

2. Pressing Tab inside the floating overlay clobbered the search query
   with the suggestion's `value` field — which the Finder library uses
   as an opaque index ("0", "1", …), so the input visibly became "0".
   Fix: in view/prompt_input.rs, short-circuit the Tab handler when
   prompt.overlay is true (arrows already navigate suggestions, so Tab
   has no other useful job in overlay mode).

Verified in tmux: Alt+/ → type "split_active" → Tab leaves the input
unchanged ("split_active", 1/31 still selected); Alt+Q exports to
the dock and the top tab bar shows only "README.md" while the dock
holds only "*Quickfix*".

https://claude.ai/code/session_01FJMAjx2SQXjqsrmW1ojCXr
sinelaw pushed a commit that referenced this pull request May 24, 2026
Re-point the issue #2056 specs from the measured (buggy) behavior to
the agreed target behavior. The fix is reverted on this branch, so
these are intentionally RED (TDD spec-first) until it lands.

Desired behavior pinned:
- `fresh <project>` activates the project-rooted window; a worktree
  session (root != cwd) is never activated by passing the project dir
  and survives as an inactive shell.
- working_dir == active_window().root (no boot-time inconsistency).
- cross-project: the other project's window is preserved, not dropped
  by an id-1 collision with the clean-base fallback.
- v1 legacy migrates, then still activates a clean base at the cwd.
- the file explorer roots at the ACTIVE window and re-roots on a dive
  (defect #3), instead of being keyed off a global working_dir and
  sticking to its first-init root.

Red specs (7): v2_worktree_session_does_not_hijack_plain_launch,
v2_cross_project_only_boots_clean_base_and_preserves_other,
v2_base_and_worktree_activates_the_base,
v1_legacy_percwd_migrates_then_activates_base_at_cwd,
launch_in_project_roots_rendered_ui_at_project,
diving_between_windows_roots_the_ui_at_the_active_window,
observe_rendered_root_with_orchestrator_plugin_loaded.

https://claude.ai/code/session_01R6SPCEgzj4HWZBSe2VZPZM
sinelaw pushed a commit that referenced this pull request May 24, 2026
…on notes

Maps the bring-up pipeline (construct -> restore -> file-explorer init ->
orchestrator) end-to-end and identifies where small data-flow changes
collapse the branch/state explosion for issue #2056:

- Editor.working_dir duplicates active_window().root (its own doc says
  so), hand-synced at 7 sites + construction; the construction site is
  the one that does NOT sync, which is the entire #2056 bug class.
  Recommend deriving working_dir from active_window().root.
- init_file_explorer reads working_dir instead of the window's own root,
  contradicting the Window.file_explorer doc (defect #3).
- the launch pick matches project_path OR root (two identities); should
  match root only.
- clean-base fallback reuses id 1 and drops a colliding persisted window.
- restore is implemented twice (first-run vs restart) and the harness
  mirror already drifted.

Also audits single->multi-window migration leftovers: working_dir is the
one straggler that should be per-window-derived; session_name is the
server/attach session (unrelated to orchestrator windows), worth
renaming to reduce confusion.

https://claude.ai/code/session_01R6SPCEgzj4HWZBSe2VZPZM
sinelaw pushed a commit that referenced this pull request May 24, 2026
… active window

Issue #2056. Two changes that green the worktree-hijack specs:

- pick_active_window_for_cwd now matches a persisted window by its
  `root` (where the window opens), not `project_path`. An orchestrator
  worktree session has project_path == parent project but root ==
  worktree, so the old project_path match resurrected a worktree-rooted
  window when launching `fresh <project>`. Matching on root means the
  active window always opens at the launch cwd; worktree sessions stay
  inactive shells, divable via the orchestrator. project_path remains
  orchestrator-dialog grouping metadata only.

- init_file_explorer roots the (per-window) tree at the active window's
  own `root` instead of the global working_dir (defect #3), so the
  explorer always reflects the window it belongs to and follows a dive.

Greens 6 of the 7 #2056 specs; the cross-project id-1 collision case is
handled separately (it needs base-session id decoupling).

https://claude.ai/code/session_01R6SPCEgzj4HWZBSe2VZPZM
sinelaw pushed a commit that referenced this pull request May 24, 2026
….root

The file explorer is per-window state; its constructor now lives on
`impl Window` and builds from `self.root` using the window's own
`resources` (runtime/fs/authority) + `bridge`. A Window has no access
to any other project's path, so "explorer rooted at the wrong project"
is unrepresentable (issue #2056 defect #3), not merely avoided.

Editor::init_file_explorer is now a thin delegator to
`active_window_mut().init_file_explorer()`; callers unchanged.

cargo check + fmt clean.

https://claude.ai/code/session_01R6SPCEgzj4HWZBSe2VZPZM
sinelaw pushed a commit that referenced this pull request May 26, 2026
…e complete

Tested: TC-025 (Save As), TC-027-029 (tabs), TC-034 (cut), TC-036 (block select),
TC-037 (comments), TC-038 (auto-indent), TC-043 (Find Prev), TC-048-049 (search
toggles), TC-055 (file explorer), TC-056-058 (line nums/wrap/terminal), command
palette coverage (TC-062/065), and BUG-006 reproduction (not reproduced ×2).

Key discoveries: Ctrl+PgDn/PgUp = buffer switching; Ctrl+E = explorer focus;
Save As = File menu only; Line Wrap toggle = View menu only; close-buffer prompt
needs letter + Enter; preview tabs auto-open during explorer navigation.

Added Run #3 lessons (8-11) to run_log, updated all test cases to PASSED,
updated learning_db false-positive patterns and key bindings, added IMP-010/011.

https://claude.ai/code/session_01W7YDfJdcwaEGvBBzsQpmBD
sinelaw pushed a commit that referenced this pull request May 27, 2026
A mouse click on a session row fires `select` with the clicked item's
key (not the list key "sessions"), so the handler ignored it. Accept the
click by also matching the payload's `list_key`, so clicking a row
selects + live-switches it (and the focus event re-arms the dock).

Add e2e: "/"+filter+Enter-returns-to-list (#5/#6), Space multi-select
(#4), and mouse-click-row-then-Space-checks-that-row (#3). Make the
order-stability test deterministic (sibling project dirs under one parent
so the project-path sort is fixed) — the earlier flake was random tempdir
paths, not the dock.
sinelaw pushed a commit that referenced this pull request May 27, 2026
A mouse click on a session row fires `select` with the clicked item's
key (not the list key "sessions"), so the handler ignored it. Accept the
click by also matching the payload's `list_key`, so clicking a row
selects + live-switches it (and the focus event re-arms the dock).

Add e2e: "/"+filter+Enter-returns-to-list (#5/#6), Space multi-select
(#4), and mouse-click-row-then-Space-checks-that-row (#3). Make the
order-stability test deterministic (sibling project dirs under one parent
so the project-path sort is fixed) — the earlier flake was random tempdir
paths, not the dock.
sinelaw pushed a commit that referenced this pull request May 31, 2026
A mouse click on a session row fires `select` with the clicked item's
key (not the list key "sessions"), so the handler ignored it. Accept the
click by also matching the payload's `list_key`, so clicking a row
selects + live-switches it (and the focus event re-arms the dock).

Add e2e: "/"+filter+Enter-returns-to-list (#5/#6), Space multi-select
(#4), and mouse-click-row-then-Space-checks-that-row (#3). Make the
order-stability test deterministic (sibling project dirs under one parent
so the project-path sort is fixed) — the earlier flake was random tempdir
paths, not the dock.
sinelaw pushed a commit that referenced this pull request Jun 2, 2026
#3: the project tag and git summary were positioned by a plugin-side
width estimate (dockDefaultWidth), so a user-dragged dock width left them
stranded at the old column. Lay lines 1-2 out as host rows with a flex
spacer between the left group and the right tag instead — the host
computes the fill at the card's real render width, so the tags stay
flush-right and re-flow on resize with no estimate. Drops the now-dead
justify()/contentW width math; caps the branch so a long one doesn't
push the right-aligned git summary off the row tail.
sinelaw pushed a commit that referenced this pull request Jun 3, 2026
…restore

Survey of how multiplexers, terminal emulators, IDEs, process-checkpoint
tools, and agent orchestrators handle restore, each checked against a real
implementation:
  1. keep process alive (tmux, iTerm2, VS Code reconnect, Superset pty-daemon
     fd-handoff [verified from source], claude-squad, Fresh detach)
  2. re-launch from saved command (2a bare: tmux-resurrect/zellij;
     2b native resume: herdr/cmux/Conductor/VS Code revive)
  3. screen snapshot only (tmux capture-pane, zellij viewport, iTerm2 OS
     restore, Fresh backing-file)
  4. process checkpoint/restore (CRIU/DMTCP) — rejected
  5. app-level reconstruction from transcript (Conductor/Crystal/Vibe Kanban)
Maps where Fresh sits (#1 + #3 today) and why 2b is the gap to fill.
sinelaw pushed a commit that referenced this pull request Jun 8, 2026
…shift-wheel h-scroll

- #2: opening a comment from the panel in side-by-side now rebuilds the
  composite focused on the comment's hunk (parameterized buildCenterComposite
  with a focus-hunk index) instead of staying at the first hunk — the unified
  cursor jump is inert when the composite is showing.
- #3: comments rail is narrow by default (~15% width; diff/comments split
  0.74 -> 0.82).
- #4: Shift+mouse-wheel over the side-by-side area now pans horizontally —
  handle_horizontal_scroll scrolls the composite's per-pane left_column
  (it only touched the split viewport, which the composite render ignores).
- #1: pin the FILES cursor to the selected row's start on every focus path
  (incl. mouse via buffer_activated), so it can't land horizontally scrolled.

https://claude.ai/code/session_01D1vLAnwKHqmebN9qRQ9E47
sinelaw pushed a commit that referenced this pull request Jun 8, 2026
…h-scroll (#4)

- test_review_comments_rail_is_narrow: the COMMENTS header starts past
  column 130 of a 160-col screen (rail ~15%).
- test_review_side_by_side_shift_wheel_scrolls_horizontally: Shift+wheel on
  the composite pans it (rendered content changes from the left-edge state).

(#2 comment-jump and #1 file-focus h-scroll are verified manually in tmux;
the harness can't drive the async comment-jump rebuild / the residual #1
edge reliably.)

https://claude.ai/code/session_01D1vLAnwKHqmebN9qRQ9E47
sinelaw pushed a commit that referenced this pull request Jun 8, 2026
…shift-wheel h-scroll

- #2: opening a comment from the panel in side-by-side now rebuilds the
  composite focused on the comment's hunk (parameterized buildCenterComposite
  with a focus-hunk index) instead of staying at the first hunk — the unified
  cursor jump is inert when the composite is showing.
- #3: comments rail is narrow by default (~15% width; diff/comments split
  0.74 -> 0.82).
- #4: Shift+mouse-wheel over the side-by-side area now pans horizontally —
  handle_horizontal_scroll scrolls the composite's per-pane left_column
  (it only touched the split viewport, which the composite render ignores).
- #1: pin the FILES cursor to the selected row's start on every focus path
  (incl. mouse via buffer_activated), so it can't land horizontally scrolled.

https://claude.ai/code/session_01D1vLAnwKHqmebN9qRQ9E47
sinelaw pushed a commit that referenced this pull request Jun 8, 2026
…h-scroll (#4)

- test_review_comments_rail_is_narrow: the COMMENTS header starts past
  column 130 of a 160-col screen (rail ~15%).
- test_review_side_by_side_shift_wheel_scrolls_horizontally: Shift+wheel on
  the composite pans it (rendered content changes from the left-edge state).

(#2 comment-jump and #1 file-focus h-scroll are verified manually in tmux;
the harness can't drive the async comment-jump rebuild / the residual #1
edge reliably.)

https://claude.ai/code/session_01D1vLAnwKHqmebN9qRQ9E47
sinelaw pushed a commit that referenced this pull request Jun 22, 2026
CONTRIBUTING #3 (semantic waiting) violations in the vi e2e suite. The
repeat-change-word test asserted buffer content immediately after `.`,
which replays the recorded change (cW → insert "X" → Esc) through the
async command pipeline in stages: WORD delete, then text insert. The bare
assert could observe the intermediate "X four.five" between those stages,
failing against the expected "X X four.five" (seen flaking on Windows CI).

The same shape — a buffer-mutating command sent via send_vi_key followed
by a bare assert_buffer_content with no wait — also appears in four paste
(`p`) sites. Sibling paste/repeat tests already wait
(wait_for_buffer_content / wait_for_rendered_lines_in_order); these did
not. Convert all of them to wait_for_buffer_content so the assertion waits
indefinitely for the operation to settle instead of racing it.

Audited the rest of vi_mode.rs and vi_mode_bugs.rs: remaining
assert_buffer_content calls are either preceded by mode/cursor semantic
waits, assert unchanged content on no-op/read-only operations, or follow
send_vi_operator_motion (which waits for the resulting mode) — not the
same race. vi_mode_bugs.rs already uses wait_for_buffer_content throughout.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LgNDJLwfN6MtVcAo3kLwQR
sinelaw pushed a commit that referenced this pull request Jun 22, 2026
…assert

CONTRIBUTING #3 (semantic waiting) again, this time on cursor position.
The paragraph-motion tests wait on cursor_position() after each `j`
(send_vi_key only sends + renders; the motion lands through the async
pipeline), but then asserted the result of the `}` / `{` paragraph motion
with a bare assert_eq!(cursor_position(), N) — no wait — so the assert
could read the pre-motion position and fail intermittently
(test_vi_vim_compat_paragraph_down_at_eof_without_trailing_newline_stays_on_last_char
was the reported flake).

Convert all six such cursor asserts to wait_until(cursor_position() == N),
matching the `j`-motion waits already used in the same tests.

Left vi_mode_bugs.rs:372 alone: it asserts the *initial* cursor position
(0) as a precondition before any motion, and the motion below it already
uses wait_until.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LgNDJLwfN6MtVcAo3kLwQR
sinelaw pushed a commit that referenced this pull request Jun 22, 2026
CONTRIBUTING #3 (semantic waiting) violations in the vi e2e suite. The
repeat-change-word test asserted buffer content immediately after `.`,
which replays the recorded change (cW → insert "X" → Esc) through the
async command pipeline in stages: WORD delete, then text insert. The bare
assert could observe the intermediate "X four.five" between those stages,
failing against the expected "X X four.five" (seen flaking on Windows CI).

The same shape — a buffer-mutating command sent via send_vi_key followed
by a bare assert_buffer_content with no wait — also appears in four paste
(`p`) sites. Sibling paste/repeat tests already wait
(wait_for_buffer_content / wait_for_rendered_lines_in_order); these did
not. Convert all of them to wait_for_buffer_content so the assertion waits
indefinitely for the operation to settle instead of racing it.

Audited the rest of vi_mode.rs and vi_mode_bugs.rs: remaining
assert_buffer_content calls are either preceded by mode/cursor semantic
waits, assert unchanged content on no-op/read-only operations, or follow
send_vi_operator_motion (which waits for the resulting mode) — not the
same race. vi_mode_bugs.rs already uses wait_for_buffer_content throughout.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LgNDJLwfN6MtVcAo3kLwQR
sinelaw pushed a commit that referenced this pull request Jun 22, 2026
…assert

CONTRIBUTING #3 (semantic waiting) again, this time on cursor position.
The paragraph-motion tests wait on cursor_position() after each `j`
(send_vi_key only sends + renders; the motion lands through the async
pipeline), but then asserted the result of the `}` / `{` paragraph motion
with a bare assert_eq!(cursor_position(), N) — no wait — so the assert
could read the pre-motion position and fail intermittently
(test_vi_vim_compat_paragraph_down_at_eof_without_trailing_newline_stays_on_last_char
was the reported flake).

Convert all six such cursor asserts to wait_until(cursor_position() == N),
matching the `j`-motion waits already used in the same tests.

Left vi_mode_bugs.rs:372 alone: it asserts the *initial* cursor position
(0) as a precondition before any motion, and the motion below it already
uses wait_until.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LgNDJLwfN6MtVcAo3kLwQR
sinelaw pushed a commit that referenced this pull request Jun 24, 2026
Replaces the per-frame reconnect poller with an event-driven path and
unifies the two reconnect mechanisms, and makes a plain `fresh ssh://…`
launch a first-class remote session. Three connected changes:

#1 Uniform backend spec. `Authority::session_spec()` derives the
   persistable `SessionAuthoritySpec` from the live authority's
   `command_wrap` (SSH/Kube → `RemoteAgent`, local/container → `Local`),
   and `editor_init` uses it to fill a window's `authority_spec` when the
   resolved one is `Local`. A CLI `ssh://` launch previously left the
   spec `Local`, which made workspace persistence and the manual-reconnect
   rebuild (`start_remote_reconnect`, which matches on the spec) silently
   inert for it. Never downgrades an already-remote spec.

#2 Event-driven reconnect. `AgentChannel` gets a stable `id` and a
   `reconnect_notify` fired on each `replace_transport` hot-swap. New
   `FileSystem::remote_channel_id` / `remote_reconnect_notify` expose
   them; a lazily-spawned per-window forwarder turns a hot-swap into
   `AsyncMessage::RemoteReconnected { connection_id }`. This replaces the
   per-frame `is_remote_connected()` poll — it wakes the loop
   immediately, carries identity, and can't miss a fast flap.

#3 One reconnect handler. `handle_remote_reconnected` maps the connection
   id back to its window and calls the new `reattach_window`, which clears
   the disconnect indicator and respawns the embedded terminals that died
   with the old carrier. The app-level `RemoteAttachMode::Reconnect`
   rebuild path now routes through the same `reattach_window`, so both the
   silent hot-swap and the manual/dormant rebuild converge on one
   idempotent path (respawn skips live terminals). Drops the old
   `remote_was_connected` field and the double-respawn guard.

The e2e test now drives the reconnect dispatch (id→window→reattach),
covering revive, wrong-id no-op, and duplicate-event idempotency.

Note: true remote-terminal *session* survival across a reconnect (a
server-side multiplexer so the shell and its processes outlive the
carrier, rather than respawning a fresh shell) is left to a separate
effort.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SHCFsZ3ReFxbqBgjwwBPdG
sinelaw pushed a commit that referenced this pull request Jun 24, 2026
Replaces the per-frame reconnect poller with an event-driven path and
unifies the two reconnect mechanisms, and makes a plain `fresh ssh://…`
launch a first-class remote session. Three connected changes:

#1 Uniform backend spec. `Authority::session_spec()` derives the
   persistable `SessionAuthoritySpec` from the live authority's
   `command_wrap` (SSH/Kube → `RemoteAgent`, local/container → `Local`),
   and `editor_init` uses it to fill a window's `authority_spec` when the
   resolved one is `Local`. A CLI `ssh://` launch previously left the
   spec `Local`, which made workspace persistence and the manual-reconnect
   rebuild (`start_remote_reconnect`, which matches on the spec) silently
   inert for it. Never downgrades an already-remote spec.

#2 Event-driven reconnect. `AgentChannel` gets a stable `id` and a
   `reconnect_notify` fired on each `replace_transport` hot-swap. New
   `FileSystem::remote_channel_id` / `remote_reconnect_notify` expose
   them; a lazily-spawned per-window forwarder turns a hot-swap into
   `AsyncMessage::RemoteReconnected { connection_id }`. This replaces the
   per-frame `is_remote_connected()` poll — it wakes the loop
   immediately, carries identity, and can't miss a fast flap.

#3 One reconnect handler. `handle_remote_reconnected` maps the connection
   id back to its window and calls the new `reattach_window`, which clears
   the disconnect indicator and respawns the embedded terminals that died
   with the old carrier. The app-level `RemoteAttachMode::Reconnect`
   rebuild path now routes through the same `reattach_window`, so both the
   silent hot-swap and the manual/dormant rebuild converge on one
   idempotent path (respawn skips live terminals). Drops the old
   `remote_was_connected` field and the double-respawn guard.

The e2e test now drives the reconnect dispatch (id→window→reattach),
covering revive, wrong-id no-op, and duplicate-event idempotency.

Note: true remote-terminal *session* survival across a reconnect (a
server-side multiplexer so the shell and its processes outlive the
carrier, rather than respawning a fresh shell) is left to a separate
effort.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SHCFsZ3ReFxbqBgjwwBPdG
sinelaw added a commit that referenced this pull request Jul 1, 2026
…me history

Addresses the remaining round-2 review findings.

#3 — The Rust git-index BFS (app/git_index.rs) scanned one directory level
deeper than the TypeScript discoverSubRepos it is documented to mirror, so a
sub-repo exactly 4 levels below the workspace root got its index watched but
was never decorated. Reframe the walk in terms of the level being scanned so
both sides scan levels 1..=3, and spell out the shared contract in both docs.

#6 — git_find_file listed every entry by its absolute path (long, shared
prefix, worse fuzzy ranking) because label and open-path were the same string.
Split into { rel, abs }: display/match the repo-relative path, open the
absolute one. Also resolve the repo via resolveGitRepo so it works from a
sub-project buffer when the workspace root isn't itself a repo.

#7 — git_blame fetched historical content with `git show <rev>:<abs-path>`,
which is fatal for an absolute path and fell through to the *current*
working-tree content. Refer to the file as `<rev>:./<name>` from its own
directory so git resolves it cwd-relative for any nesting.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
sinelaw added a commit that referenced this pull request Jul 7, 2026
…me history

Addresses the remaining round-2 review findings.

#3 — The Rust git-index BFS (app/git_index.rs) scanned one directory level
deeper than the TypeScript discoverSubRepos it is documented to mirror, so a
sub-repo exactly 4 levels below the workspace root got its index watched but
was never decorated. Reframe the walk in terms of the level being scanned so
both sides scan levels 1..=3, and spell out the shared contract in both docs.

#6 — git_find_file listed every entry by its absolute path (long, shared
prefix, worse fuzzy ranking) because label and open-path were the same string.
Split into { rel, abs }: display/match the repo-relative path, open the
absolute one. Also resolve the repo via resolveGitRepo so it works from a
sub-project buffer when the workspace root isn't itself a repo.

#7 — git_blame fetched historical content with `git show <rev>:<abs-path>`,
which is fatal for an absolute path and fell through to the *current*
working-tree content. Refer to the file as `<rev>:./<name>` from its own
directory so git resolves it cwd-relative for any nesting.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file rust Pull requests that update Rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants