Skip to content

fix: queue the file children the tick's folder leg lists so a remote add paints its size - #1802

Merged
FSM1 merged 3 commits into
mainfrom
fix/1800-tick-queues-listed-file-children
Sep 6, 2026
Merged

fix: queue the file children the tick's folder leg lists so a remote add paints its size#1802
FSM1 merged 3 commits into
mainfrom
fix/1800-tick-queues-listed-file-children

Conversation

@FSM1

@FSM1 FSM1 commented Sep 6, 2026

Copy link
Copy Markdown
Owner

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_scope read
focus.open_files at the top of the pass, so a child the folder leg listed later
in 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::SetFocus and the mount's access check, and neither fires while the
folder 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.

  • After the folder leg settles for a scope, the pass queues the size-less file
    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.
  • Only the folders in view queue, not every folder target. An ancestor rides the
    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_FILES bound. The open folder
    queues last for the same reason, because the bound drops the oldest entry.
  • A scope whose only folder in view is its own root groups no folder target,
    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_FILES stays the bound on the whole pass, which is what
    blueprint/desktop.md states. The queue refills on every leg, so leg_file_share
    charges the budget across the legs and drops any row an earlier leg already
    attempted. The on_access_refresh_due damping, the attempted_files retain
    rule and settle_focus_leg are unchanged. Files of a shared scope stay on that
    scope's own leg.
  • The two legs no longer settle from one array of pre-awaited reports, because
    the queueing step has to see the folder leg's result. They settle through one
    local closure over the same settle_focus_leg.
  • One implementation of each rule: the bodies of queue_focus_file_children,
    note_focus_file and scoped_to moved to free functions the tick body calls
    directly, 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 a
    folder with Command::SetFocus and never navigates again; device B writes a
    file 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 the
    vault 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 has
    published no version costs one resolve per stale_after, not one per tick. The
    count 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_budget and
    a_legs_file_share_drops_what_the_pass_already_attempted cover the pass-wide
    bound 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, and pnpm lint:tracker-refs.

Review gates

  • /simplify: four angles, twelve findings. Folded: one shared nodes_in_scope
    for the scope filter, used by the tick legs and by scoped_to; the file targets
    read straight from the focus queue instead of a second cross-scope grouping; the
    loop variable no longer mutable; a record_resolves test helper in place of two
    copies 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 FocusQueue view type to carry the three borrows
    is 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 refills
    the queue on every scope leg, so the burst became MAX_FOCUS_FILES per leg
    rather than per pass, which is the bound blueprint/desktop.md "Freshness"
    states. leg_file_share now charges one budget across the pass and drops a row
    an 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 a
    trust rejection or the reverse; no RefCell borrow is held across an await; the
    clock 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, no
    nonce, 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 burst
    stays bounded per tick by MAX_FOCUS_FILES and each file is damped by
    stale_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

  1. The tick, not a host access, is now what re-queues an unpainted row. The
    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_after for the rows that stay unpainted, with
    no 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.
  2. A promoted scope root the boundary walk named but could not prove is absent
    from the proved set, so scope_root_of groups its subtree onto the enclosing
    scope 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.
  3. focus_refreshed holds one stamp per node a pass attempted and is never
    pruned. 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.
  4. queue_focus_file evicts the oldest queue entry, so the tick's own bulk
    queueing 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

  • Splits the combined folder/file refresh loop into separate folder legs and file legs, and gives every in-view scope a file leg including a scope represented only by its root
  • After each folder leg settles, queues direct file children that have no projected size through the same staleness-damped, bounded queue used by on-access candidates
  • Shares one MAX_FOCUS_FILES budget across all file legs in a pass and drops nodes already attempted by an earlier leg
  • Extracts nodes_in_scope, queue_unprojected_children, queue_focus_file, and leg_file_share helpers so the tick pass and existing Engine methods reuse the same logic
  • Behavioral Change: Engine::scoped_to, Engine::queue_focus_file_children, and Engine::note_focus_file now delegate to shared helpers; semantics are preserved but the focus refresh pass no longer retries a file within the same pass

Macroscope summarized 9d81f84.

Summary by CodeRabbit

  • Bug Fixes
    • Improved focus refresh behavior across folders and scopes, including folders currently in view.
    • Ensured folder targets refresh before newly discovered child files are queued.
    • Prevented repeated file attempts within the same refresh cycle and balanced file processing across scopes.
    • Added automatic file metadata updates when open folders or roots receive files from another device.
    • Reduced unnecessary updates for unwritten rows during staleness-window processing.

FSM1 added 3 commits September 6, 2026 22:38
…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.
@coderabbitai

coderabbitai Bot commented Sep 6, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: a55be355-f38d-4eb0-87ea-97d4cee5c39c

📥 Commits

Reviewing files that changed from the base of the PR and between 2c4d63a and 9d81f84.

📒 Files selected for processing (2)
  • crates/engine/src/facade.rs
  • crates/engine/tests/write_plane.rs

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


Walkthrough

Focus 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.

Changes

Focus refresh processing

Layer / File(s) Summary
Shared focus-file queue helpers
crates/engine/src/facade.rs
Centralized scope filtering, child queueing, staleness damping, bounded ordering, and focus-file notification.
Scoped tick refresh settlement
crates/engine/src/facade.rs
Added folder-only scopes, settled folder targets before file targets, shared the pass-wide file budget, and excluded previously attempted files.
Refresh behavior validation
crates/engine/src/facade.rs, crates/engine/tests/write_plane.rs
Added coverage for budget sharing, attempted-file exclusion, same-pass file metadata painting, and unwritten-row staleness windows.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Merge Risk: ⚪ Minimal · up to 9d81f

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
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the primary fix: queueing file children listed by the tick's folder leg so remote additions receive their size.
Linked Issues check ✅ Passed The changes satisfy issue #1800. The tick queues size-less file children after the folder leg, processes file targets in the same pass, preserves the bounded file budget and damping behavior, and adds…
Out of Scope Changes check ✅ Passed The changes remain within issue #1800. The helper extraction, scope filtering, queue management, budget handling, and related tests directly support the required focus refresh behavior.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 7 functions across 1 files. (1 skipped: 1 …
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/1800-tick-queues-listed-file-children

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.

@FSM1
FSM1 marked this pull request as ready for review September 6, 2026 21:07
@FSM1

FSM1 commented Sep 6, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 6, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@FSM1

FSM1 commented Sep 6, 2026

Copy link
Copy Markdown
Owner Author

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.

@FSM1
FSM1 merged commit f272115 into main Sep 6, 2026
37 checks passed
@FSM1
FSM1 deleted the fix/1800-tick-queues-listed-file-children branch September 6, 2026 21:18
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.

engine: a file another device adds to an open folder never paints its size until the folder is re-entered

1 participant