Skip to content

fix: reads at a commit the branch has left, a hidden review, and the agent panel's Import - #50

Merged
ragnorc merged 6 commits into
mainfrom
fix/studio-sweep-20261031
Oct 31, 2026
Merged

ragnorc merged 6 commits into
mainfrom
fix/studio-sweep-20261031

Conversation

@ragnorc

@ragnorc ragnorc commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

These come from a wider latency, efficiency and correctness review of Studio beyond the sign-in code. There's one commit per fix, six in all. Each fix has a test that fails without it.

Reads at a commit the branch has left (graph_store)

A read at the head held that the server answered at another commit was read again at its commit, and so was every read at the old head until the next poll, up to 30 s. Each one read the branch and then the commit (table pages, tree groups, degree batches, node names), and each branch answer, up to 32 MiB, was downloaded and thrown away. Reads at that commit now go straight to it until the head watch moves the head.

The head itself is left to the head watch, unlike what spec 015's target choice suggests. The other commit isn't known to be newer: a server replica may be behind, or Studio's own commit may not be served yet. A first version probed the head on such an answer, and that moved the head back and took Studio's own write for another writer's; five type_table tests caught it.

The branch list read once (graph_store)

Taking a schema probed the heads and read the branch list again, so about half of all opens and Refreshes read it twice. Taking a schema now probes the heads alone.

Only one list read is ever out (no poll beside a reload, and a reload drops a poll under way). So the numbering that guarded against an older list landing last could never fire, and it's removed. Its test passed only because of the second read; it now covers the order that can actually happen.

A review behind another tab (review)

A new token cleared the review but kept its read under way. A drawn review replaced that read at once. A review behind another tab kept it, and when it landed it showed the old token's cards, with Revert and Edit, until the review was shown.

The agent panel's Import and Restart (agent_panel)

  • Import still asked the agent for the graph's folder alone, though conversations now start in folders of their own. It found none of them, said the list had them all, and recorded the graph's folder, where the agent couldn't load a conversation. It now asks for each conversation folder and records each conversation's own.
  • Restart of a conversation nothing was sent in left its folder on disk. Choosing another agent already removed it; Restart now does too.

A last page the change cap cut (review)

The feed's fold counts a review as partial whenever the cap left changes out. The diff's fold did only when the server named another page, so a last page with more changes than asked for, cut at the cap, showed as the whole review. It now follows the feed's rule.

The model list (studio_workspace, model)

The list was read from disk twice on the main thread at startup and again at every key change. Refreshes could overlap, and the request had no size or time limit. Now its read time is kept in memory, one refresh runs at a time, and the request is capped at 16 MiB and 30 s.

Cleanups (type_table, history, review, gq_language)

  • Two lookups that were quadratic in list length are now linear.
  • One "same held schema" rule replaces two hand-written copies.
  • An always-false parameter is gone.

Testing

  • cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace (1,163 passed) and the repo checks pass locally.
  • New or changed tests:
    • session::tests::a_read_at_the_head_goes_to_the_branch_and_checks_its_commit (extended)
    • a_graph_opened_reads_its_branch_list_once (new)
    • an_older_branch_list_landing_last_does_not_replace_a_newer_one (reordered)
    • review a_hidden_reviews_read_under_the_old_token_does_not_land (new) and a_last_page_past_the_cap_is_partial (new)
    • agent_panel import_asks_every_conversation_folder_and_keeps_each_ones_own (new) and starting_an_unsent_conversation_again_removes_its_folder (new)
    • model a_model_list_past_its_cap_is_refused (new)
  • Specs 011 and 015 are updated, with log rows.

Considered and left

  • The connection dot redrawing the workspace every 3 s: moving the beat into a view of its own wouldn't help. GPUI's mark_view_dirty marks a notifying view and every view above it dirty, so the workspace redraws either way. A notify every 3 s is already cheaper than an animation, which requests a frame on every refresh.
  • The CLI home's cleanup on the main thread: a few file removals when a conversation stops, outside any frame. That's neither the expensive work nor the in-frame I/O that rule IX of the constitution keeps off the main thread.
  • Refresh restarting a head probe already in flight: deliberate, so the head read starts at or after the click.

… list once

A read at the head held that the server answered at another commit was
read again at its commit, and so was every read at the old head until the
next poll, up to 30 s, each reading the branch and then the commit. Reads
at that commit now go to it at once until the head watch moves the head.
The head itself is left to the watch: the other commit is not known to be
newer (a server replica behind, or Studio's own commit not yet served), and
probing it moved the head back and took Studio's own write for another
writer's.

Taking a schema probed the heads and read the branch list again, so an
open or Refresh whose list answered before its schema read it twice. It
now probes the heads alone; a reload reads the list beside the schema, and
every poll reads it.

Only one list read is ever out: no poll goes beside a reload, and a reload
drops a poll under way. The numbering that guarded against an older list
landing last could never fire, so it is removed. Its test passed only
through the second read, and now covers the order that can happen.
A new token cleared the review and dropped its search, names and dry run,
but not the read of the review itself. A drawn review replaced it at once.
A review behind another tab kept it, and when it landed it showed the old
token's cards, with Revert and Edit, until the review was shown.
…art cleans up

Since conversations started in folders of their own, Import still asked
the agent for the graph's folder alone and kept only exact matches. So it
found none of them, said the list already had them all, and recorded the
graph's folder, where the agent could not load the conversation. It now
asks for the graph's folder and each folder in it (an agent lists sessions
by exact folder), keeps the sessions that ran in the folder asked about,
and records each one's own folder.

Restarting a conversation nothing was sent in claimed a new folder and
left the old one on disk, with its AGENTS.md and skill. Choosing another
agent already removed it; Restart now does too.
The model store read models.json twice on the main thread at startup and
again at every key change, to learn when the list was read. It now keeps
that time. A key changed while a refresh is out no longer starts a second
one.

The list's request had no size or time limit, unlike a call. It is now
capped at 16 MiB (the list carries a few megabytes of descriptions) and
gives up after 30 s.
- The table removed scrolled-away Blob reads from its queue with one pass
  over the queue per key removed; it now makes one pass.
- History looked each drawn row's merged parents up in a list on every
  frame; it now uses a set, as the read that follows already does.
- Review and the commit view each wrote out whether two held schemas are
  the same; `SchemaModel::same_held` says it once, beside `same_as`.
- The peek's `load_group` took an `open` flag that every caller passed as
  false.
The feed's fold counts a review as partial whenever the cap left changes
out. The diff's fold did only when the server named another page, so a
last page with more changes than asked for, cut at the cap, was shown as
the whole review. It now follows the feed's rule.
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