fix(panel): count the Tickets badge as rendered groups, and cut the tab's refresh cost - #143
Merged
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The Tickets tab got slow as the handoff ledger grew. Measured on a real 90-day
ledger (446 records, 97 groups, 244 distinct repo+branch pairs across 32 repos):
What was wrong
VStackbuilt every group headerand every branch sub-row on each render. The selection index is published
state the view reads, so holding an arrow key rebuilt all 345 rows per keypress.
refreshPullRequestswas per branch. Twogit remote get-urlspawns andone blocking GraphQL request per branch, then a single dictionary key written
back on main each time, so one refresh caused 249 whole-panel re-renders.
refreshOutcomesresolved refs per branch. Tworev-parses plus a re-walkof the base-branch candidates for every branch.
onAppear, so flipping to the tabre-paid the whole cost.
it sits above the mode switch.
Changes
Each commit typechecks standalone.
perf(panel): render the Tickets list lazily—LazyVStack, matching theSessions and Events lists, plus an allocation-free walk to the selected row id.
perf(panel): scan events once, for the selected session only— the twoper-row scans of the event store become one, and only the selected row needs
it, since it is the only row that renders the nudge line.
fix(panel): count the Tickets badge as rendered groups— see below.perf(panel): batch and throttle the Tickets tab refreshes— one aliasedGraphQL query per 25 branches per repo; one
show-refper repo instead ofper-branch
rev-parse; outcomes cached against the git values they werederived from; both refreshes behind a coalescing rate limiter.
Behaviour changes
ticket ?? branch, counting every unticketed branch separately, while the tabbuckets unticketed work per repo; one checkout alone contributed 34 branches
counted 34 times and always shown as one row. The badge and the tab footer now
agree by construction. The list itself is unchanged.
and it is keyed on branch tips so it updates on commit/push/pull, not a timer).
Explicit actions bypass the limit: enabling GitHub linking and completing
sign-in fetch immediately. If 2 minutes reads as stale, the follow-up is an
R-key manual refresh on that tab rather than a shorter window.Ship-status semantics are deliberately unchanged, including the existing
treatment of a session recorded on a detached
HEADand the stale-local-mainweakness in a fork workflow. Both are worth revisiting separately.
Verification
swift testcannot run on the authoring machine (Command Line Tools has noXCTest), so this leaned on other checks:
working copies: all 244 repo+branch pairs produce identical status, and the
warm-cache pass reproduces the cold pass exactly. This caught a real regression
before it shipped:
for-each-refonly matches patterns underrefs/, so itnever reports
HEAD, and the four sessions recorded on a detachedHEADweresilently becoming "deleted branch" and flipping
committedtomerged. Henceshow-ref --head, verified attached and detached.scrollTocannot resolvean id nested inside a row a
LazyVStackhas not built, and these anchors liveinside the group card. Measured by which rows realise: an id on the direct child
moved the viewport, the nested id moved nothing, which would have broken
Cmd-Down. Each card now carries a coarse anchor and the scroll takes two hops.
query construction against hostile branch names (slashes, quotes, braces,
non-ASCII), batch boundaries,
show-refparsing, base-branch precedence,cache invalidation on commit/push/pull/new-Stop/branch-delete, and the rate
limiter driven by an injected clock.
build.shgreen; runninglocally against a live ledger.
Considered and rejected
upsertcosts 4.6ms medianon main, once per agent turn, on a ledger capped at 1000 records. Async needs
two injected scheduler seams or a weaker durability contract to save that. The
async version was written, measured, and reverted.
Session.elapsedand folding the twopscalls into one. Theamplifier is gone, the list is lazy, and the parser relies on
commbeing theline's remainder while executables like
Code Helper (Renderer)contain spaces,so a combined line cannot be split unambiguously. Real risk, no measurable gain.
the retry that should happen the moment an org authorises the OAuth app.
Unrelated finding
39 private repos log
Could not resolve to a Repository, which is also whatGitHub returns for a repo the token cannot see. The public repo never fails. The
app requests the
reposcope, so this looks like org-level third-party OAuth apprestrictions rather than anything in this change, and it predates it. Worth its
own issue. Batching does cut the resulting log spam, which had reached 55k lines,
since errors were logged once per branch per refresh.