Fix/open page by slug missing md projection - #126
Conversation
avelino
left a comment
There was a problem hiding this comment.
Two changes are riding in one PR. The #120 fix (desktop + mobile) is the right shape and mirrors open_ref cleanly. The TUI slash-palette reorder is the problem: it's the same change as #125, which is still in review for the exact bug it still carries here.
Split it. Keep #126 as the #120 fix only, move the TUI part back to #125. Then #126 merges once the sidecar churn below is handled.
blocking
- The TUI reorder only syncs the empty-query case. With an active query, arrow navigation still walks candidates in score order while the renderer always buckets by category, so
↓can move the highlight up. Same bug I flagged on #125.
needs fixing
open_page_by_slugnow projects.md+ sidecar on every open.build_sidecarstampslast_synced_at: now(), so the.outlrewrites on every page open even when the.mdis byte-identical. This is the hottest nav path, so it turns into sync churn. Guard it to only project when the file is missing.
No new test for #120 either. A cross-category navigation test locks the palette regression; a "synced page opens with content" test locks #120.
| // so a missing file produces an empty outline — the page opens blank. | ||
| // Mirrors the `open_ref` fix. | ||
| with_ws_mut(&state, |ws| { | ||
| if let Err(e) = outl_actions::apply_page_md_with_sidecar(ws, &root, id) { |
There was a problem hiding this comment.
This projects on every open. build_sidecar stamps last_synced_at: now(), so the .outl gets rewritten every time you open a page, even when the .md is identical. That's pure sync churn on the hottest nav path.
Guard it: only project when pages/<slug>.md is absent (resolve via page_meta + journal::page_md_path). Same fix on the mobile mirror at line 141. Worth an issue to guard open_ref the same way, it has the same unconditional projection and nobody caught the churn there.
When a new device joins a sync chain, peer ops are replayed into the
in-memory CRDT tree via reload_workspace. This makes every synced page
visible in search (list_pages reads the tree). However, the .md file
for those pages is never written to the new device's disk during
reload_workspace — only today's journal gets reproject_page called on
it.
open_page_by_slug then calls build_page_view, which calls
read_page_outline, which does:
fs::read_to_string(&md_path).unwrap_or_default()
A missing file silently returns "", parse("") produces an empty
outline, and the page opens blank — no error, no blocks.
The fix mirrors what open_ref already does: call
apply_page_md_with_sidecar before building the view. This writes the
.md + sidecar to disk on first open if they are absent, so subsequent
reads find the correct content.
Affected surfaces: iOS (open_page_by_slug via picker / PageSwitcher)
and desktop (same command, same code path).
Fixes: page appears in search but shows empty body after joining a
sync chain.
3f6f445 to
5e02527
Compare
Replace the unconditional apply_page_md_with_sidecar call in open_page_by_slug (desktop + mobile) with a new guarded helper apply_page_md_with_sidecar_if_absent that projects only when the .md is missing from disk. Root cause of the churn: build_sidecar stamps last_synced_at: now() on every call, so the unconditional variant rewrote the .outl sidecar on every page open even when the .md was byte-identical — constant sync noise on the hottest nav path for every file-transport peer (iCloud / Syncthing). The guard lives in outl-actions so both clients share one owner: - absent .md → project (fixes outlmd#120 — synced page opens blank) - present .md → no-op (fixes the sidecar churn) Two new unit tests in outl-actions::journal lock both behaviours: - apply_if_absent_projects_when_md_is_missing: page in CRDT tree, no .md on disk → helper writes projection → read_page_outline returns the peer's content, not empty. - apply_if_absent_is_noop_when_md_already_exists: .md present → returns None, sidecar bytes unchanged after the call. Closes outlmd#120 Refs outlmd#127 (open_ref has the same unconditional projection — follow-up)
open_ref projected .md + sidecar on every call. build_sidecar stamps last_synced_at: now(), so each ref-click rewrote the .outl file even when the .md was byte-identical. file-transport users (iCloud / Syncthing) see every click as a write that lands on the other device. swap to the _if_absent variant introduced in #126: projects only when the .md is missing (new page case still works), skips when it already exists. desktop and mobile share the same body in outl-tauri-shared, so the one-line change covers both clients. Fixes #127 Signed-off-by: Avelino <31996+avelino@users.noreply.github.com>
What this PR does
When a new device joins a sync chain, the peer ops are replayed into the in-memory CRDT tree by
reload_workspace, making every synced page visible in search — but only today's journal gets its.mdfile projected to disk.open_page_by_slugthen callsbuild_page_view→read_page_outline, which doesfs::read_to_string().unwrap_or_default(): a missing file silently returns"",parse("")produces an empty outline, and the page opens blank with no error. The fix callsapply_page_md_with_sidecarbefore building the view (mirroring whatopen_refalready does), so the.md+ sidecar are written to disk on first open if absent.How to verify
cargo test --workspace --all-targets cargo clippy --workspace --all-targets -- -D warnings cargo fmt --all -- --checkManual smoke (iOS + macOS):
Cmd+P/ search) — pages from device A should appear.Related issues / docs
Closes #120
Anything reviewers should look at carefully
.mdprojection before a read; it does not touch op generation,apply_op, or the tree structure.apply_page_md_with_sidecaris the same writer used byopen_refand the journal renderer; the output format is unchanged.warn!/eprintln!is emitted andbuild_page_viewstill runs (returning empty content as before, rather than crashing).Out of scope for this PR
reload_workspace(would fix the same symptom for any navigation path, but could be expensive on large workspaces — separate issue).open_journal_forcommand has a similar shape but journal pages are always projected at boot byreproject_page, so it is not affected.'