Skip to content

Search the dez rail by workspace, session, kind, or directory - #22

Merged
Oreoro merged 5 commits into
mainfrom
feature/dez-rail-search
Oct 4, 2026
Merged

Oreoro merged 5 commits into
mainfrom
feature/dez-rail-search

Conversation

@Oreoro

@Oreoro Oreoro commented Oct 3, 2026

Copy link
Copy Markdown
Owner

Summary

The rail is dez's only supervision surface, and it lists every workspace group plus every live session under it. That stops being scannable well before dez's own multi-project, multi-session workflow does. v0.6 had a filter here and the port dropped it; this restores it.

  • A single-line editor under the rail header narrows the rail to the groups and rows whose text matches, and highlights the matched characters where they sit.
  • A session is searched by what its row shows plus the two things it does not: the agent kind behind the row's actor label, and the directory it was started in.
  • A group whose own name matched keeps all of its rows, so asking for a workspace answers with that workspace rather than only with its sessions that happen to match.
  • Attention-first ordering and the header's Needs-input rollup are computed over the filtered rows, so the count beside "Workspaces" always describes what is on screen.
  • "No workspaces" and "nothing matched" get different empty-state copy, because they are fixed by different user actions.

Why not the fuzzy crate

Matching is a synchronous subsequence scan, ported from v0.6's fuzzy_match_positions and widened from contiguous substring to subsequence. It is deliberately not fuzzy::match_strings, which is async: the rail rebuilds every row on each render, so a background pass would race its own keystrokes and can land out of order, and the rail holds tens of rows rather than thousands. One forward scan per field is cheaper than the machinery to cancel and reorder one.

The scan prefers a continued run, then a word start, so fps highlights a readable span in "Fix bug in panel sidebar" instead of scattered characters. Offsets are always UTF-8 character boundaries, which is what keeps a multi-byte title from tripping HighlightedLabel's debug assertion.

Keyboard

cmd-f / ctrl-f focuses the field via a new MultiWorkspace::focus_sidebar_search, which opens a closed rail first. It is bound in the Workspace key context rather than globally, so an open editor keeps its own cmd-f — the same scoping v0.6 used.

Sidebar gains a focus_search with a default that delegates to prepare_for_focus, so a sidebar without a search field is unaffected.

Rules

  • Ports behavior, not the old file: no agent_threads API additions, no state-machine change (MERGE.md §2.2).
  • Design-doc box ticked in docs/dez-workspace-shell.md, one SYNC-LEDGER.md line, two FORK.md rows.
  • 15 unit tests in crates/dez_sidebar/src/filter.rs cover the matcher: substring, subsequence, case in both directions, run-vs-loose preference, out-of-order rejection, repeated characters, non-ASCII boundaries, hidden-field admission, and blank-query normalization.

Verification

Not built locally by request — CI is the build. cargo fmt was run with rustfmt directly on the touched files (parse-clean). Expect clippy and the tests job to be the real gate.

Suggested .rules additions

  • The dez rail rebuilds every row during render, so anything the rail filters on must match synchronously. fuzzy::match_strings is async and will race its own keystrokes there; a scan in crates/dez_sidebar/src/filter.rs is the pattern.
  • DezSidebar::new takes a &mut Window because Editor::single_line needs one. Constructors in this repo that build an Editor cannot be called from a plain &mut App — check before assuming a signature can stay windowless.

Release Notes:

  • Added a search field to the workspace rail that filters by workspace name, session title, agent kind, and working directory, highlighting matches in place. Bound to cmd-f / ctrl-f.

The rail lists every workspace group and every live session under it,
which stops being scannable well before dez's own multi-project,
multi-session workflow does. v0.6 had a filter here and the port dropped
it; this brings it back.

A single-line editor under the rail header narrows the rail to the
groups and rows whose text matches, and highlights the matched
characters where they sit. A session is searched by what its row shows
plus the two things it does not: the agent kind behind the row's actor
label, and the directory it was started in. A group whose own name
matched keeps all of its rows, so asking for a workspace answers with
that workspace rather than with only its sessions that happen to match.

Matching is a synchronous subsequence scan, ported from v0.6's
`fuzzy_match_positions` and widened from contiguous substring to
subsequence. It is deliberately not the async `fuzzy` crate: the rail
rebuilds every row on each render, so a background pass would race its
own keystrokes and can land out of order, and the rail holds tens of
rows rather than thousands. The scan prefers a continued run, then a
word start, so `fps` highlights a readable span in "Fix bug in panel
sidebar" instead of scattered characters. Attention-first ordering and
the header's rollup are computed over the filtered rows, so the count
beside "Workspaces" always describes what is on screen.

`cmd-f` / `ctrl-f` focuses the field through a new `MultiWorkspace`
route that opens a closed rail first. It is bound in the `Workspace`
key context rather than globally so an open editor keeps its own
`cmd-f`. Reopening a rail that already holds a query returns focus to
the field, so closing it mid-search does not throw the query away.

Ports behavior, not the old file: no `agent_threads` API changes, and
the state machine is untouched.
@coderabbitai

coderabbitai Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 44b55227-e342-4b0f-b55f-85f946715845
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 3 advisory finding(s) below merit a look before merge.


Graphify review — findings

Adds a search field under the rail header that filters workspaces and sessions by name, title, agent kind, or working directory, highlighting matched characters in place while keeping attention-first ordering. Matching is a synchronous subsequence scan that prefers continuing a run, then a word start. A whitespace-only query counts as no filter, and a group whose own name matches keeps all its rows. cmd-f / ctrl-f in the Workspace context focuses the field via MultiWorkspace::focus_sidebar_search, opening a closed rail first, without stealing find from an open editor.

Worth a look

  • Caller passes Option<&mut Window> (already moved) to DezSidebar::new, which now requires &mut Window — crates/dez/src/dez.rs:410 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Greedy word-start preference makes match_positions reject candidates that contain the query — crates/dez_sidebar/src/filter.rs:55 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Group hidden when only a multi-root workspace label matches the search — crates/dez_sidebar/src/sidebar.rs · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review

Review partial — this diff was larger than one review pass covers, so later files were not reviewed; some findings may be missing.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 1307 functions depend on the 748 functions this change touches.

Health — grade A; 10 existing hotspot(s) in the area this change touches (pre-existing, not introduced here):

  • init() — 52 callers, 3 callees (high)
  • init_test() — 38 callers, 4 callees (high)
  • spawn_live_codex_thread() — 20 callers, 6 callees (high)
  • init_test_with_editor() — 40 callers, 3 callees (high)
  • init_test() — 18 callers, 6 callees (high)
  • init_test() — 27 callers, 3 callees (high)
  • init() — 10 callers, 8 callees (high)
  • init_test() — 10 callers, 7 callees (high)
  • …and 2 more

Verification — 1307 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 1303 function(s) in the blast radius were not formally verified this run

Docs that may be stale (advisory)

…and 10 more.

Oreoro added 4 commits October 4, 2026 04:20
`Option<Vec<usize>>` on the row structs was carrying no information:
`session_title` drew a plain label for `None` and a `HighlightedLabel`
with no highlights for `Some(vec![])` -- the same pixels. The fields are
plain `Vec<usize>>` now and there is one label shape, which also removes
the possibility of the two drifting apart.

Also drops an `aria_label` that `IconButton` does not have in this fork
(the tooltip already names the button), and gives the two empty-state
arms the same tuple type.
observe_new hands the closure an Option<&mut Window> and the existing
early return bound it as _window, so the call site passed the Option
itself into a constructor that needs the window.
Neither was a bug in the matcher; both were my expectations being wrong
about it, and both are worth stating correctly because the precedence
they describe is the whole point of the scan.

"at" on "atlas data" cannot demonstrate the word-start preference: "at"
is the front of "atlas", so continuing the run resolves it at [0, 1]
before the word-start preference is ever consulted. Replaced with "ab"
on "axb b", which does isolate it -- the `b` inside "axb" comes first but
is mid-word, so the preference skips it for the one that opens a word,
giving [0, 4] where a plain first-match scan gives [0, 2]. Added the run
wins case and a separator case alongside, so all three rungs of the
preference are pinned.

`match_positions` returns one offset per query character, not per byte,
so "café" is four offsets; the assertion was summing `len_utf8()`.
@Oreoro
Oreoro merged commit 5c17d88 into main Oct 4, 2026
18 checks passed
@Oreoro
Oreoro deleted the feature/dez-rail-search branch October 4, 2026 04:42
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.

1 participant