fix: queue the file children the tick's folder leg lists so a remote add paints its size - #1802
Conversation
…add paints its size The tick fixed its file targets before its folder leg ran, so a file another device added to a folder held in focus was listed but never resolved. The pass now queues the size-less file children of the folders in view after the folder leg settles, then takes that scope's file targets from the queue and runs the file leg in the same pass. Part of #1702
…cope filter Folds the simplify pass: one scope-filter helper for the tick legs and the navigation path, the file targets read straight from the focus queue instead of a second cross-scope grouping, and a scope whose only folder in view is its own root now gets a pass of its own.
The queueing step refills the focus queue on every scope leg, so a pass could spend MAX_FOCUS_FILES resolves per leg. The blueprint bounds the burst per tick. Each leg now takes only what earlier legs left of that budget, and never a row the pass already attempted.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Team Run ID: 📒 Files selected for processing (2)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. WalkthroughFocus refresh processing now uses shared scope and file-queue helpers. Tick processing refreshes folders first, queues newly visible file children, shares the bounded file budget across scopes, and excludes files already attempted in the pass. Tests cover metadata projection and staleness damping. ChangesFocus refresh processing
Estimated code review effort: 3 (Moderate) | ~25 minutes Merge Risk: ⚪ Minimal · up to Open folders and vault roots now populate metadata for remotely added files during the next refresh tick while retaining bounded refresh work and staleness damping. No actionable current-head merge risk remains. Sequence Diagram(s)sequenceDiagram
participant Tick
participant FolderRefresh
participant FocusFileQueue
participant FileRefresh
Tick->>FolderRefresh: refresh folder targets by scope
FolderRefresh->>FocusFileQueue: queue unprojected file children
FocusFileQueue->>FileRefresh: provide eligible files within remaining budget
FileRefresh->>Tick: refresh files and record attempted targets
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
Review disposition: the CodeRabbit pass at 21:17Z returned a clean verdict. All three surfaces are empty — no inline threads, no "Outside diff range" section, and no "Nitpick" section — so there is nothing to disposition and no change was made after the pass. The three repository review gates ran before the PR opened; their verdicts and the four residual items are in the PR body. |
Symptom
The web holds a folder open. Another device adds files to it. The new rows
appear in the listing within one poll cadence, but their size and modified time
stay on the placeholder for as long as the folder stays open. The sizes paint
only when the user leaves the folder and enters it again.
Cause
The tick fixed its file targets before its folder leg ran.
focus_by_scopereadfocus.open_filesat the top of the pass, so a child the folder leg listed laterin the same pass was neither queued nor in that pass's file targets. Nothing
queued it afterwards either: the queueing step ran on two paths only,
Command::SetFocusand the mount's access check, and neither fires while thefolder stays open.
Change
The tick body now runs the three steps the navigation path has run since #1785,
in that order, per scope: the folder leg, then the queueing step, then the file
leg.
children of the folders in view in that scope. It then takes that scope's file
targets from the queue and runs the file leg in the same pass.
folder leg to walk the child gate, not to be painted, and its rows would evict
the open folder's rows under the
MAX_FOCUS_FILESbound. The open folderqueues last for the same reason, because the bound drops the oldest entry.
because that root resolves on its pointer leg. Such a scope now gets a pass of
its own, so a file added to the open root folder paints like any other.
MAX_FOCUS_FILESstays the bound on the whole pass, which is whatblueprint/desktop.mdstates. The queue refills on every leg, soleg_file_sharecharges the budget across the legs and drops any row an earlier leg already
attempted. The
on_access_refresh_duedamping, theattempted_filesretainrule and
settle_focus_legare unchanged. Files of a shared scope stay on thatscope's own leg.
the queueing step has to see the folder leg's result. They settle through one
local closure over the same
settle_focus_leg.queue_focus_file_children,note_focus_fileandscoped_tomoved to free functions the tick body callsdirectly, since it holds no
self. The three methods delegate.No wasm change and no client change.
Tests
Integration tests in
crates/engine/tests/write_plane.rs:a_tick_paints_a_file_another_device_added_to_the_open_folder: device A opens afolder with
Command::SetFocusand never navigates again; device B writes afile into it and publishes; one tick on device A paints the new row's size and
modified time.
a_tick_paints_a_file_another_device_added_to_the_open_root: the same, with thevault root in view, which is the window that groups no folder target of its own.
a_tick_resolves_an_unwritten_row_once_per_staleness_window: a row that haspublished no version costs one resolve per
stale_after, not one per tick. Thecount comes from the snapshot-cache reads under the row's own record name, over
three ticks of the CI profile.
Unit tests in
crates/engine/src/facade.rs:a_legs_file_share_stays_inside_the_passes_own_budgetanda_legs_file_share_drops_what_the_pass_already_attemptedcover the pass-widebound and the one-leg-per-row rule, which a single-scope integration scenario
cannot reach.
Mutation checks: with the queueing step removed, the first and third tests fail;
with the scope seeding removed, the second fails. Both steps were restored and
all tests pass again.
Gates green:
cargo fmt --all --check,cargo clippy --workspace --all-targets -- -D warnings,cargo test -p cipherbox-engine,cargo test -p cipherbox-fuse,cargo check -p cipherbox-wasm --target wasm32-unknown-unknown, andpnpm lint:tracker-refs.Review gates
/simplify: four angles, twelve findings. Folded: one sharednodes_in_scopefor the scope filter, used by the tick legs and by
scoped_to; the file targetsread straight from the focus queue instead of a second cross-scope grouping; the
loop variable no longer mutable; a
record_resolvestest helper in place of twocopies of the same read counter; the scope-root window covered rather than left
as a hole; and three comments trimmed to rationale that is not stated at its
home. Rejected with reason: a
FocusQueueview type to carry the three borrowsis speculative generality for two callers, since the free functions already keep
one implementation; a full unification of the tick and navigation paths is a
separate change, because the two differ in scope filtering, in the grafted
plane, in verdict aggregation and in when the queue drains; the per-child queue
scan and the child walk stay, because the safe alternatives are not clearly
better at the current bounds.
/security-review: one finding at the bar, folded. The queueing step refillsthe queue on every scope leg, so the burst became
MAX_FOCUS_FILESper legrather than per pass, which is the bound
blueprint/desktop.md"Freshness"states.
leg_file_sharenow charges one budget across the pass and drops a rowan earlier leg attempted. Verified clean: every queued row still passes the
adoption gate on the same child path; a node cannot reach a leg holding another
scope's read seed; a scope with no recovered seed still serves nothing, and the
new seeding cannot open a leg that was closed before; the only new verdict is
Unreachable, which is availability, so no availability failure reads as atrust rejection or the reverse; no
RefCellborrow is held across an await; theclock enters only through the injected scheduler; no key or seed material
appears in a log or an assertion.
/crypto-privacy-review: no cryptographic finding. The diff adds no seal, nononce, no AAD construction and no KDF edge, and it does not touch
crates/core.Scope routing is sound: the leg's seed and the node filter come from the same
scope root, and a mis-scoped record fails closed in the child gate. The privacy
exposure stays the class
blueprint/desktop.md"Freshness" describes: the burststays bounded per tick by
MAX_FOCUS_FILESand each file is damped bystale_after. What widens is the trigger, recorded under Residual.Manual check
The web has a folder open. Add a file to that folder from the desktop. The size
paints within one poll cadence, with no navigation on the web.
Residual
documented exposure class does not change: names, kinds, sizes and bodies stay
sealed, and co-resolution of a folder's children was already the focus-window
design. What changes is the trigger. A folder left open emits the sibling-set
resolve pattern once per
stale_afterfor the rows that stay unpainted, withno user action, where before it emitted them once per access. This is the
on-access model applied to the folder the user is still looking at, so it is
recorded rather than treated as a defect. The
blueprint/desktop.md"Freshness" bullet still names the stat as the re-queue driver, and a wording
change there belongs with the decision corpus, not with this fix.
from the proved set, so
scope_root_ofgroups its subtree onto the enclosingscope and the records fail their unseal there. That already reported an honest
writer as attributable abuse on the folder leg; this change carries it to the
file children of those folders and raises the count. The repair is a
classification one, of the same shape as fix: report a seal failure above the local read-epoch floor as availability, not abuse #1793: group by the proved and the
unproved roots together, so such a node maps to a root that holds no seed and
the leg is skipped as availability. That belongs in its own change against the
fail-closed rules.
focus_refreshedholds one stamp per node a pass attempted and is neverpruned. The tick now stamps file rows without a host access, so a long session
with a large folder in view accretes entries faster than before. The map is
bounded by the node count the base holds. A pruning rule belongs with the
focus-window state, not with this fix.
queue_focus_fileevicts the oldest queue entry, so the tick's own bulkqueueing can drop a row a host stat queued earlier before any leg attempts it.
The row keeps rendering last-known-good and returns on the next stat. This is
the eviction rule the bound has always had; it is now reachable from the tick
as well.
Closes #1800.
Note
Queue unprojected file children listed by focus refresh folder legs so remote adds paint their size
MAX_FOCUS_FILESbudget across all file legs in a pass and drops nodes already attempted by an earlier legnodes_in_scope,queue_unprojected_children,queue_focus_file, andleg_file_sharehelpers so the tick pass and existingEnginemethods reuse the same logicEngine::scoped_to,Engine::queue_focus_file_children, andEngine::note_focus_filenow delegate to shared helpers; semantics are preserved but the focus refresh pass no longer retries a file within the same passMacroscope summarized 9d81f84.
Summary by CodeRabbit