Skip to content

Perf/pipeline optimizations - #88

Open
Mx-Iris wants to merge 23 commits into
mainfrom
perf/pipeline-optimizations
Open

Perf/pipeline optimizations#88
Mx-Iris wants to merge 23 commits into
mainfrom
perf/pipeline-optimizations

Conversation

@Mx-Iris

@Mx-Iris Mx-Iris commented Aug 8, 2026

Copy link
Copy Markdown
Member

No description provided.

Mx-Iris added 8 commits August 4, 2026 16:18
Every (supposedly debounced) keystroke in the runtime-object sidebar ran
the full filter cascade synchronously on the main thread and reset
`filterResult` on every row, whose didSet rebuilt the attributed title
unconditionally — ~3.5 s of main-thread freeze per keystroke at 10k rows
(20k title rebuilds on clear). Worse, the "500 ms debounce" never
functioned: `.just(...).debounce(...)` flushes the pending element the
moment the single-element source completes.

- FilterEngine: extract a pure, thread-safe `match(_:haystacks:)` core;
  merge query/case/mode into `FilterContext: Equatable`; fix the inverted
  case-sensitivity branch in contains mode
- SidebarRuntimeObjectCellViewModel: nil→nil guard on `filterResult`,
  equality guard on `filterContext`, cached `currentAndChildrenNames`
  with upward invalidation on child splices
- SidebarRuntimeObjectFilterPipeline (new): snapshot (main) → verdicts
  (background, cooperative cancellation) → apply (main), replicating the
  legacy per-level ordering semantics (twin-tree parity test)
- SidebarRuntimeObjectViewModel: `scheduleRefilter()` with generation
  tokens; broken debounce replaced by a working `delay(150 ms)` +
  flatMapLatest cancellation; empty queries keep the synchronous fast path
- SidebarRuntimeObjectListViewModel: replace the uncancelled Task.detached
  Open Quickly search (two inflight searches raced on the same cell
  view models and a stale result could clobber a fresh one) with
  generation-guarded scheduling; stop cascading highlights into
  never-displayed child cells
- SidebarRuntimeObjectViewController: the case-sensitivity button starts
  .on so the effective default stays case-insensitive now that the engine
  honors the flag

Measured (debug, N = 10k): contains keystroke 3654 → 54–81 ms with 0
title rebuilds; clear 3763 → 17–20 ms; fuzzy narrow 4132 → 331–423 ms;
seeded reload 7429 → 227–293 ms. The new regression suite pins the
per-keystroke rebuild counts.

Docs: Documentations/Plans/2026-08-04-sidebar-filter-pipeline-perf.md
ContentTextViewModel ran a single combineLatest(object, options, theme,
transformer) → XPC fetch → main-thread NSAttributedString build. Every
font-size/theme tweak paid a full XPC round-trip for an interface that
does not depend on the theme, then rebuilt the whole attributed string on
the main thread. This lands PR1 of the 2026-05-17 plan.

- Fetch half: object / options / transformer (distinctUntilChanged) →
  engine; theme no longer participates. Render half: latest interface ×
  latest theme → background-scheduler build → main-thread bind;
  flatMapLatest drops superseded builds on font-size click bursts
- Move `catchAndReturn(nil)` inside the inner fetch sequence: on the
  outer chain it completed the whole pipeline on the first fetch error,
  permanently freezing the tab's content (regression test added)
- Observable.tracking: never resolve @dependency inside the access
  closure — the re-arm hop runs on a bare main-queue dispatch, drops
  task-locals, and re-resolves against the ambient default context,
  silently swapping in a wrong instance and killing the chain. Both call
  sites (ResolvedThemeStream, the transformer stream) now capture the
  Settings instance at arm time; the contract is documented on the bridge
- SemanticString builder returns an immutable copy (cross-thread handoff
  contract for the background-built string)
- Core: GenerationOptions gains Equatable; RuntimeObjectInterface gains a
  public memberwise init (test stubs)
- Signposts content.interfaceFetch / content.attributedStringBuild
  (category Content.TextPipeline) gate the follow-up PR2/PR3 decisions

Tests: ContentTextPipeline suite — a theme-only change keeps the fetch
count at 1 while re-rendering with the new font size, a failed fetch
recovers on the next options change, and the render helper output is
byte-equal to the direct builder invocation (PR2 restyle baseline).

Docs: Documentations/Plans/2026-08-04-content-text-pipeline-pr1.md; the
2026-05-17 plan status now records PR0+PR1 as landed, PR2/PR3 gated on
signpost measurements.
Every navigation step (link click, tab switch, back/forward) rebinds a
fresh ContentTextViewModel, and each rebind re-fetched the interface
over XPC — revisiting an object always paid full price, and a single
link click actually fetched twice (resolution with bare default
options, then display with the user's merged options under a
different key).

Add a per-document RuntimeInterfaceCache (LRU 16, keyed by object +
merged generation options) and route every single-object fetch
through it: the content pipeline's fetch half, both link-resolution
flows (now using the same merged options, so resolution warms the
entry the post-push display fetch hits — one round-trip per click),
and MainViewModel's save/share paths. Concurrent lookups share one
in-flight task; nil results and errors are never cached; engine swaps
and dataChangePublisher events flush everything, with a generation
token so a straggler fetch can never repopulate a flushed cache.

Routing save/share through the merged options also fixes an existing
inconsistency: exported text could differ from the displayed text
because the transformer configuration never participated there.

GenerationOptions and its members gain Hashable (additive, synthesized)
to serve as cache keys. Regression suites cover hit/miss, coalescing,
invalidation (including the real reloadData broadcast wiring), LRU
eviction, and a navigation-revisit integration test.
swift-testing runs tests concurrently across suites while every
DocumentState in the target shares RuntimeEngine.local. The real-engine
flush test broadcasts .fullReload into whatever other suite happens to
be mid-assertion, and the engine's first connect() replays one startup
.fullReload from an unstructured Task at an arbitrary moment early in
the run — both flakes only became visible once the test count grew.

Adds withSharedLocalEngineLock (mutual exclusion for broadcasters and
broadcast-sensitive tests) plus a one-shot startup barrier that waits
out the engine's bring-up traffic, and wires the three existing
sensitive tests through them.
Every image reload eagerly built a second full copy of the sidebar's
cell view models for Open Quickly (icons, attributed titles, child
trees) — ~250 ms of main-thread work per 10k rows in a debug build,
paid even by sessions that never open the panel.

The reload now stores only the sorted RuntimeObject array. Matching
runs off-main against pure haystack strings (computed once per reload,
byte-identical to the cell's own haystack so fuzzy highlight ranges
still map — pinned by a parity test), and only matched rows materialize
into cell view models, cached by row index so keystrokes reuse
instances and DifferenceKit keeps stable row identities.

The class drops `final` so tests can seed the real reload path, same
as its superclass.
Root-sidebar keystrokes ran a recursive localizedCaseInsensitiveContains
cascade over the whole image tree on the main thread, including the
first-use recursive aggregate-name concatenation — thousands of nodes
per keystroke on a dyld shared cache tree.

Replaces the didSet cascade with a SidebarRootFilterPipeline mirroring
the runtime-object pipeline's shape (snapshot on main, verdicts on the
global executor, generation-guarded apply on main) while replicating
the root tree's own legacy semantics: aggregate-contains matching, and
a node whose own name matches shows its subtree unfiltered. Aggregate
names now build inside the off-main verdict pass, so the cell's lazy
aggregate property (and its main-thread first-use cost) is gone.
Parity with the legacy semantics is pinned against an independent
reference implementation.
Every itemDidExpand/itemDidCollapse notification walked all rows and
wrote UserDefaults. An option-click "expand all" posts one notification
per expandable item, turning the burst into O(rows squared) row visits
plus a defaults write per item.

The persist is now scheduled once per burst and flushed on the next
main-queue turn, with the preconditions re-checked at flush time. A
package-visible persist counter seams the coalescing for the regression
test (the test target gains a RuntimeViewerUI dependency for it).
One landing doc for the three fixes (Open Quickly lazy materialization,
root-sidebar off-main pipeline, outline autosave coalescing) plus the
cross-suite test-isolation discovery, and marks the sidebar plan's
follow-up items 1 and 4 as landed.
Copilot AI lite review requested due to automatic review settings August 8, 2026 04:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR focuses on eliminating main-thread stalls by moving several UI-critical pipelines (sidebar filtering, root sidebar filtering, Open Quickly search, content text rendering) off-main, and by introducing a per-document interface cache so navigation/save/share flows don’t repeatedly round-trip to the runtime engine.

Changes:

  • Introduces off-main “snapshot → verdict → apply” filter pipelines for both the runtime-object sidebar and the root image tree, plus keystroke coalescing and cancellation/generation guards.
  • Adds a per-document RuntimeInterfaceCache and routes single-object interface fetches (content, save/share, link resolution) through it using merged generation options.
  • Coalesces StatefulOutlineView expansion autosave to avoid O(N²) bursts; adds comprehensive regression/perf test suites and supporting test infrastructure.

Reviewed changes

Copilot reviewed 37 out of 37 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
RuntimeViewerUsingAppKit/RuntimeViewerUsingAppKit/Sidebar/RuntimeObject/SidebarRuntimeObjectViewController.swift Sets case-insensitive toggle default to preserve prior effective behavior.
RuntimeViewerUsingAppKit/RuntimeViewerUsingAppKit/Main/MainViewModel.swift Save/share now uses interface cache + merged options for consistency and cache hits.
RuntimeViewerPackages/Tests/RuntimeViewerApplicationTests/StatefulOutlineViewAutosaveTests.swift Regression tests for coalesced expansion autosave behavior.
RuntimeViewerPackages/Tests/RuntimeViewerApplicationTests/SidebarRootFilterPipelineTests.swift Root sidebar pipeline parity + end-to-end VM tests.
RuntimeViewerPackages/Tests/RuntimeViewerApplicationTests/SidebarFilterPerformanceBaselineTests.swift Baseline/perf regression assertions for sidebar filter hot path.
RuntimeViewerPackages/Tests/RuntimeViewerApplicationTests/SharedLocalEngineTestLock.swift Cross-suite lock + startup barrier for RuntimeEngine.local interference.
RuntimeViewerPackages/Tests/RuntimeViewerApplicationTests/RuntimeInterfaceCacheTests.swift Contract tests for interface cache (LRU, invalidation, in-flight coalescing).
RuntimeViewerPackages/Tests/RuntimeViewerApplicationTests/OpenQuicklyLazyConstructionTests.swift Tests for Open Quickly lazy row materialization + haystack parity.
RuntimeViewerPackages/Tests/RuntimeViewerApplicationTests/MockRouter.swift Router test double to assert navigation side effects.
RuntimeViewerPackages/Tests/RuntimeViewerApplicationTests/ContentTextPipelineTests.swift Content pipeline regression tests (no refetch on theme-only changes, error resilience, cache revisit).
RuntimeViewerPackages/Sources/RuntimeViewerUI/AppKit/StatefulOutlineView.swift Coalesces expansion autosave persists; adds persist-count seam.
RuntimeViewerPackages/Sources/RuntimeViewerArchitectures/Observable+Tracking.swift Documents dependency-resolution constraint inside tracking closures.
RuntimeViewerPackages/Sources/RuntimeViewerApplication/ViewModel.swift Adds currentMergedGenerationOptions for cache-key alignment and export consistency.
RuntimeViewerPackages/Sources/RuntimeViewerApplication/Theme/SemanticString+ThemeProfile.swift Ensures immutable NSAttributedString escape for cross-thread rendering.
RuntimeViewerPackages/Sources/RuntimeViewerApplication/Theme/ResolvedThemeStream.swift Resolves dependencies outside tracking closure to avoid context loss.
RuntimeViewerPackages/Sources/RuntimeViewerApplication/Sidebar/SidebarRuntimeObjectViewModel.swift Replaces synchronous cascade with cancellable off-main filter pipeline apply.
RuntimeViewerPackages/Sources/RuntimeViewerApplication/Sidebar/SidebarRuntimeObjectListViewModel.swift Open Quickly: lazy materialization, off-main matching, cancellation/generation guards.
RuntimeViewerPackages/Sources/RuntimeViewerApplication/Sidebar/SidebarRuntimeObjectFilterPipeline.swift New runtime-object tree filter pipeline (snapshot/verdict/apply).
RuntimeViewerPackages/Sources/RuntimeViewerApplication/Sidebar/SidebarRuntimeObjectCellViewModel.swift Adds haystack caching, guarded title rebuilds, and pipeline apply entry point.
RuntimeViewerPackages/Sources/RuntimeViewerApplication/Sidebar/SidebarRootViewModel.swift Root sidebar filtering moved off-main with cancellation + generation guard.
RuntimeViewerPackages/Sources/RuntimeViewerApplication/Sidebar/SidebarRootFilterPipeline.swift New root tree filter pipeline replicating legacy semantics off-main.
RuntimeViewerPackages/Sources/RuntimeViewerApplication/Sidebar/SidebarRootCellViewModel.swift Removes mutating filter cascade; adds pipeline apply entry point.
RuntimeViewerPackages/Sources/RuntimeViewerApplication/FilterEngine.swift Refactors into pure match + contextual filter; fixes case-sensitivity inversion.
RuntimeViewerPackages/Sources/RuntimeViewerApplication/DocumentState.swift Adds per-document interfaceCache.
RuntimeViewerPackages/Sources/RuntimeViewerApplication/Content/RuntimeInterfaceCache.swift New LRU cache with invalidation + in-flight coalescing semantics.
RuntimeViewerPackages/Sources/RuntimeViewerApplication/Content/ContentTextViewModel.swift Splits fetch/render halves, instruments with signposts, routes via cache/provider.
RuntimeViewerPackages/Package.swift Adds RuntimeViewerUI dependency to application tests for package seam access.
RuntimeViewerCore/Sources/RuntimeViewerCore/Core/RuntimeSwiftSection.swift Adds Hashable conformance needed for option-keyed caching.
RuntimeViewerCore/Sources/RuntimeViewerCore/Core/RuntimeObjCSection.swift Adds Hashable conformance needed for option-keyed caching.
RuntimeViewerCore/Sources/RuntimeViewerCore/Common/RuntimeObjectInterface+GenerationOptions.swift Makes GenerationOptions Equatable/Hashable for caching and distinctness.
RuntimeViewerCore/Sources/RuntimeViewerCore/Common/RuntimeObjectInterface.swift Adds public init for easier construction in cache/pipeline tests.
Documentations/Plans/2026-08-04-sidebar-filter-pipeline-perf.md Design/implementation record for sidebar filter pipeline perf refactor.
Documentations/Plans/2026-08-04-openquickly-root-outline-perf.md Design/implementation record for Open Quickly + root filter + outline autosave.
Documentations/Plans/2026-08-04-navigation-interface-cache.md Design/implementation record for navigation interface cache behavior/contract.
Documentations/Plans/2026-08-04-content-text-pipeline-pr1.md Design/implementation record for content pipeline PR1 split.
Documentations/Plans/2026-05-17-content-text-attributedstring-optimization.md Updates status to reflect implemented PR0/PR1 and measurement-gated PR2/PR3.
AGENTS.md Codifies “single-object fetch via interface cache + merged options” rule.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 408 to +410
@MainActor
private func rebuildFilteredNodes() {
let scope = scope
func scheduleRefilter() {
currentFilterTask?.cancel()
Comment on lines 77 to 83
public var children: [SidebarRuntimeObjectCellViewModel] {
get { _filteredChildren }
set {
_children = newValue
_filteredChildren = newValue
invalidateNamesCacheUpwards()
}
Comment on lines +48 to +50
/// Generation guard for `currentOpenQuicklyFilterTask` — also bumped
/// when `nodesForOpenQuickly` is rebuilt, so a match computed against
/// a discarded node array is never applied.
Mx-Iris added 7 commits August 9, 2026 21:12
Every high-cardinality cell ViewModel carried five discrete @observed
appearance properties (primaryIcon, secondaryIcon, tertiaryIcon, title,
subtitle), and each @observed costs a BehaviorRelay wrapping a
BehaviorSubject plus an NSRecursiveLock — roughly 450-500 bytes of Rx
plumbing per property for values that only change on filter edits and
specialization splices. With ~13k resident image-list rows and ~7k
browse-path rows, the per-row multiplier made NSRecursiveLock the
largest ObjC class in the process (125,225 instances after a full
browse).

Merge them into one @observed appearance struct per row, published
atomically with an equality guard so identical refreshes emit nothing.
RuntimeObjectCellDisplayable shrinks to a single appearanceDriver, cell
views bind once and fan out to their outlets in apply(_:), and the
remaining conformers (Inspector cells, the specialization type picker)
compose their structs at init.

Measured on the same five-image full-browse load (evolution proposal
0005, Implemented): NSRecursiveLock 125,225 -> 42,218, the UI/Rx heap
cluster 46.7 -> 17.7 MiB, steady state 210 -> 196.7 MiB. Behavior is
pinned by the existing filter-baseline emission counts plus new
SidebarCellAppearanceTests (one event per transition, zero events for
equal republish and display-neutral splices).
…ine flag

FilterEngine's pre-2026-08 plain-contains branch had the flag inverted
(isCaseInsensitive == true selected the case-SENSITIVE contains). When
the engine was fixed to honor the flag, the AppKit sidebar flipped its
toggle default in the same change, but the UIKit sidebar's hardcoded
.just(false) was missed — shipping iOS a case-sensitive search
(PR #88 review, finding 2; known-issue PR88.2).

Flip the constant and pin the honest semantics at the engine with
FilterEngineCaseSensitivityTests, so a future inversion fails loudly at
the source instead of silently flipping whichever platform forgot to
compensate. The engine-level suite stands in for a UIKit-side
reproduction test: that target has no test bed, and the engine semantics
are the root the regression grew from.
Splitting the content pipeline left trackActivity on the fetch half
only. With a warm interface cache the fetch is near-instant, and theme /
font-size changes skip it entirely, so every wait the user actually
perceives fell in an untracked gap and the loading indicator never
appeared (PR #88 review, finding 3; known-issue PR88.3). Track the
render half's inner sequence too — the fetch's element reaches it before
the fetch observable completes, so the indicator hands over without a
false gap.

Also hoist the ConcurrentDispatchQueueScheduler out of the flatMapLatest
closure: the convenience initializer allocates a fresh DispatchQueue, so
a burst of font-size clicks churned one queue per emission (finding 6;
PR88.6).

New test fontSizeChangeSurfacesLoadingIndicator fails against the
fetch-only placement (verified red before this fix, green after).
…pshot

scheduleRefilter() built the full snapshot forest before checking
shouldFilter, so clearing the search (and the refilter right after every
reload, when the per-cell haystack caches are cold) paid a bottom-up
O(nodes) name build whose verdicts are, by definition, the identity
(PR #88 review, finding 4; known-issue PR88.4).

Serve the fast path with a new snapshot-free resetToUnfiltered that
installs the same applyFilterOutcome the snapshot -> verdicts -> apply
chain produces for an empty context — child-before-parent, identity
child lists, no haystack reads. SidebarFilterFastPathTests pins the
equivalence against the legacy chain on the same tree shape, and that
clearing a real filter restores the identity state.
FilterEngine.filter ran match() — materializing the filterableString
array and a verdict per item — one line before the empty-query guard
discarded the result (PR #88 review, finding 5; known-issue PR88.5).
Hoist the guard above the call; the context stamping loop stays first,
pinned by FilterEngineCaseSensitivityTests.emptyQueryFilterResetsItems.
…ntract

The filter-pipeline rework (cbb589c) removed the last read of the cell
view model's appDefaults dependency; @dependency doesn't trigger unused
warnings, so the property lingered as dead weight on a ~10k-instance
class (PR #88 review, finding 11; PR88.11).

SidebarRootFilterPipeline.verdicts(for:query:) silently clears the whole
image tree for an empty query (localizedCaseInsensitiveContains("") is
false for every haystack); the sole caller's fast path upholds the
documented contract today, so assert it — a second call site is where it
would break silently (finding 8; PR88.8).
Seven findings fixed this batch (each row carries its commit), one
false positive retracted with the runtime evidence preserved — the
RxCocoa-vs-RxAppKit control-property priming boundary is the part worth
keeping — and seven backlog items with their pickup conditions. The
KnownIssues index row lands on next alongside the other doc-index rows,
since this branch predates the Documentations index.
Coalescing the expansion persist onto the next main-queue turn left a
window in which the tree could be replaced before the walk ran. The walk
describes whatever tree is installed at flush time, and a rebuilt tree
comes back fully collapsed — the root sidebar maps every `$nodes`
emission through a fresh `SidebarRootCellViewModel` whose `Differentiable`
conformance resolves `differenceIdentifier` to `self`, so every row is a
new item — so the flush collected nothing and wrote an empty array over
the user's saved state. `RuntimeEngine.reloadData` broadcasts
`.fullReload` on every image load, and `restoreExpansionFromAutosave()`
runs once per document, so the loss was both routine and permanent.

Track a monotonic structure version, bumped by every entry point that can
reshape the item tree, and sample it when the persist is scheduled; the
flush runs only while the sample still matches. Expand/collapse
notifications are delivered synchronously — `NotificationCenter` runs the
block inline when the observer queue is the posting queue — so the sample
always describes the tree the user acted on.

The incremental mutators are hooked alongside `reloadData()` because a
diffing adapter prefers them: RxAppKit only falls back to `reloadData()`
when the changeset carries `elementUpdated` entries, which an all-new row
set never does.

The guard keys on the data changing, not on the walk coming back empty —
collapsing every row is a legitimate way to persist an empty set, and the
third new test pins that.
… rebuild

The image-tree rebuild used to install the new list and invalidate the
in-flight filter pass through two separate subscriptions:
`$nodes.bind(to: $filteredNodes)` ran synchronously, while the
cancellation and generation bump went through `subscribeOnNextMainActor`,
which expands to `Task { @mainactor in … }` and therefore only enqueued
them. A verdict continuation resuming in that window saw
`Task.isCancelled == false` and its captured generation unchanged, so it
passed both guards and applied cleanly — the old array is self-consistent
with its own snapshot — republishing the discarded cell tree over the
fresh one. Nothing reschedules a filter afterwards, so the image sidebar
kept showing the previous tree until the user typed again.

Merge both halves into one synchronous `installRebuiltNodes(_:)`. The
sibling `SidebarRuntimeObjectViewModel` already bumps its generation
synchronously inside `scheduleRefilter()`, so only the root pipeline had
this hole.

The tests sample the generation from a `$filteredNodes` observer rather
than after `accept` returns: `observe(on: MainScheduler.instance)` only
delivers synchronously while the scheduler is idle, so an
"assert right after accept" test passes alone and fails under concurrent
suites.
A link click asks the engine about a synthetic target built at the click
site from the clicked token — it carries the currently displayed object's
`imagePath`, and on the ObjC arm its `children` — and the engine answers
with the defining section's authoritative `RuntimeObject`. That resolved
object is what the push navigates to and what the destination
`ContentTextViewModel` fetches under, but the entry was stored under the
requested object. `RuntimeObject`'s `Hashable` folds in `imagePath` and
`children`, so the display fetch was a guaranteed miss: two full
generations per link click, plus a dead entry occupying one of the
sixteen slots — the opposite of the one-round-trip design the link flow
documents. The Swift arm rebuilds every field, so this hit same-image
jumps too, not only cross-framework ones.

Store the ready entry under `interface.object`.
The haystacks depend only on the object list, never on the query, but the
apply task installed them behind the cancellation/generation guard — a
pass superseded by the next keystroke threw its completed build away.
Whenever the build outran the 150 ms debounce, continuous typing
discarded one full build per query and the cache never populated.

Install the build as soon as it completes, keyed to a new object-list
version counter rather than the filter generation: the generation also
moves on every keystroke, while the build is only invalid once a reload
replaces the list it was built from (installing then would misalign
every row index).

The builder is injectable now so the regression test can gate the
superseded pass's build and release it alone; releasing every gated
build would let the current pass install the cache itself, which the
old always-discard code also did, masking the regression.
…aystack

Stamping a highlight on a freshly materialized cell triggers
composedTitle(), whose cold currentAndChildrenNames rebuilt the whole
subtree name string on the main actor — the byte-identical twin of the
haystack the off-main matching pass had just computed for that same row
(the two sides are byte-for-byte equal by the parity contract pinned in
OpenQuicklyLazyConstructionTests).

Hand the pass's haystack to the cell at materialization time via a
seeding entry point on the cell view model. Seeding is a no-op once a
value is cached, so it can never contradict a locally derived haystack.
.fuzzySearch keeps every haystack with a non-zero score, and a haystack
is the object's name plus every descendant's, so a one- or two-character
query matches essentially the whole image. The apply loop then built a
cell view model — and, through rebuildChildren(), one per descendant,
each with icon lookups and an attributed title — for every row in a
single main-actor turn: the exact O(N) main-thread cost lazy
materialization exists to remove, re-paid after every reload's first
wide query, and retained in the row memo for the document's life.

fuzzyMatch returns matches sorted by descending weight, so taking the
prefix keeps the best-scoring rows; what the cap drops is the
near-zero-score tail nobody scrolls to.
Records the cross-session re-verified adjudications for review findings
F1-F15 as PR88R2.<N>: six fixed in-branch (with fix commits), the
one-tick transformer skew and the unreachable applyNodes guard
downgraded to no-fix with rationale, the specialization dead-entry claim
refuted (any dataChangePublisher event flushes the whole interface
cache), and six backlogged. Also adds the missing index rows for the
2026-08-09 and 2026-08-10 adjudication files.
Two retrospectives written while the PR #88 fixes landed but never committed;
they were sitting untracked in the branch's worktree.

The first covers the three Open Quickly performance fixes — the haystack
seeding, the superseded-pass install, and the top-500 materialization cap.
The second covers F9/F10/F11: where the test blockers were, how each fix was
verified red-then-green, and why the batch was split the way it was.

TaskReports/ is a new category on this branch. Documentations/README.md does
not exist here — this branch forked before the index landed on main, which is
also why Documentations/Evolution/ and Documentations/Evolutions/ still sit
side by side. Registering these two files in the index therefore belongs with
the rebase, alongside the documentation remediation already tracked as PR88.15
in KnownIssues/2026-08-09-pr88-review-findings.md.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants