Memoize Context::is_stale via stale_watcher, kill per-frame stats #58
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/058-memoize-context-is-stale-via-stale-watcher-kil"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Memoize Context::is_stale via stale_watcher, kill per-frame stats
Along the way:
Touches 191 files:
.claude/commands/fix.md,.claude/skills/fix.md,CLAUDE.md,Cargo.lock,Cargo.toml,ck-editor/src/highlight/cache.rsand 185 more.Reads well. One question on
ck-markdown/src/link_probe.rs, nothing blocking.@ -0,0 +147,4 @@}#[test]fn existing_file_with_fragment_strips_fragment() {existing_file_with_fragment_strips_fragmentreads, decides and writes in one function. The deciding part is the piece I would want to test on its own. Worth splitting?@ -56,2 +63,2 @@paint_blocks(&painter, origin, &laid.blocks, palette);responsepaint_blocks(&painter, origin, &laid.blocks, palette, syntax);let mut hits = Vec::new();hitsallocates a fresh buffer per call. If this runs per frame or per token, can the buffer be reused?@ -0,0 +172,4 @@0,&ctx,).expect(ERR_SPAWN_SLEEP)The expect message on
expectis the only thing a user sees if this fires. Does it say what they should do, or only what we assumed?, at ckeditor/src/project/test_support.rs:175.Split. The decision in
existing_file_with_fragment_strips_fragmentis a pure function now, with its own tests inck-markdown/src/link_probe.rs.It runs per token. The buffer behind
hitslives on the struct inck-markdown/src/paint.rsnow and is cleared rather than reallocated.It said what I assumed. Reworded in
ckeditor/src/project/test_support.rsso it names the input that broke it, which is what the bug report will need.Read it too. No objection to
ck-markdown/src/link_probe.rs.`discover_root_branch_candidate` rewritten to consult git directly before guessing from a static list. Precedence: 1. `refs/remotes/origin/HEAD` — the remote's declared default. 2. `git config init.defaultBranch` — what this repo/user set. 3. Legacy `ROOT_BRANCH_CANDIDATES` walk (master/main/root/dev). 4. Current HEAD branch. 5. Literal `ROOT_BRANCH_FALLBACK` — now `main` (2026 default), was `master`. Each step that fires logs at `debug!` with the value chosen, so a "discovery picked the wrong branch" report reproduces from a single `RUST_LOG=debug` log line. Tests: - `discover_root_branch_prefers_origin_head` — origin/HEAD wins over the candidate walk even when it points outside the list. - `discover_root_branch_uses_init_default_branch` — local `init.defaultBranch` beats the candidate walk. - `discover_root_branch_falls_back_to_main_not_master` — empty repo with detached HEAD, no upstream, suppressed init.defaultBranch: literal fallback is `main`. - Existing `discover_root_branch_picks_canonical_when_present` tightened — both `master` and `main` present, origin/HEAD points at `main`; assert `main` wins. `ckeditor-context-model.md` §3.1 + §5.8 step 3 updated to match.Add a Yank { text, linewise } record drained once per yank via take_yank(), so the app can observe yanks (for an on-yank notification and a yank-history ring) without this egui-free crate knowing about Project/Context. It is independent of the OS-clipboard slot, which the view still mirrors each frame. All three yank sites (visual y, charwise operator yank, linewise operator yank) now funnel through a single record_yank helper that fills the register, the clipboard slot, and the yank record together so they cannot drift; delete/change fill only the register, as before. Unit-tested, including that delete surfaces neither a yank event nor a clipboard copy.Split ckeditor/src/project.rs (3971 LOC) into project/{mod,persistence,worktree,buffers}.rs plus a #[cfg(test)] test_support.rs, with tests moved to the submodule they exercise. mod.rs is now 2324 LOC (-41.5%; ~885 production), clearing the roadmap §2 "40% or it was cosmetic" bar. - Widen CK019/020/021 chokepoint matchers from the single file project.rs to the whole project/ directory; each rule gains a submodule-exemption test. Contract unchanged (nothing outside the module mutates Context state) — now expressed at directory granularity. - Add CK022: per-file LOC ratchet in baseline.toml's [files] section. A file growing past its recorded ceiling fails the gate; shrinking ratchets it down. Seeded for all 114 ckeditor/src files; unit-tested in report_baseline.rs and verified end-to-end. - CK018 allow-list updated to the split files, each justified. - Metric-neutral: coupling folds to top-level project, type metrics key off the first path segment, so no [modules.*]/[types.*] value moved. Resolves docs/tickets/2026-05-26-project-rs-god-class-growth.md; closes roadmap quality-review-2026-05 §2.Replace the tabbed help modal and the single auto-generated `:help` buffer with bundled, cross-linked markdown man-pages opened as a reading-view buffer (Ctrl+?, F1, :help). Pages live in `app_layout/help/` and materialize to `$XDG_DATA_HOME/ckeditor/help/` so inter-page links resolve; shortcuts are substituted live from the `KeybindingStore` via `{{key:Action}}` tokens, with `keybindings.md` generated from `{{keybindings_table}}`. Ctrl+? is the new primary help binding (`Key::Questionmark`), dropping Ctrl+H; F1 and `:help` kept. The Ctrl+? display/parse quirk and default live in `state/keybindings/question_mark.rs`. Also bundles markdown body-font-size plumbing in the reading views (`ck-markdown` `PageConfig::with_body_font_size` across the continuous / paginated views and the notes panel). Gates: lint 0 errors / 0 warnings, baseline ratchet locked (modal deletion -730 LOC), 270 lib + 61 scenario tests green, workspace builds clean.The scenario harness's wait_until pumped a fixed frame count (PROJECT_READY_FRAMES = 30) and panicked with only that count on timeout. Both fed the recurring scenario flakes: a frame budget silently tightens to a smaller wall-clock bound under pre-commit CPU saturation (the 2026-05-20 / 2026-05-22 flake tickets), and "condition not met within 30 frames" gives nothing to debug from. - wait_until is now (label, budget: Duration, cond): it pumps until the condition holds or a wall-clock deadline passes. Wall-clock, not frames, because the awaited work (watcher debounce, LSP, git subprocess) advances on the clock and on other threads. READY_BUDGET (2s) replaces the 30-frame constant for the standard helpers; wait_until_head_oid_starts_with takes its own Duration, and the git remote scenario passes REMOTE_OP_BUDGET (5s) — targeting the ticket's "HEAD did not advance within 60 frames" shape. - On timeout it panics with the label plus a WaitSnapshot of where it hung (project open? active context? active buffer + dirty?), e.g. `wait_until("buffer foo.rs open") timed out after 2s (pumped N frames): ...`. The report builder is pure (takes the snapshot, not the app) so it is unit-testable without booting CkEditorApp. Adds 5 tests: 3 for WaitSnapshot::render, 1 for the report framing, and 1 that drives the real timeout path (never-true cond + 30ms budget) to exercise WaitSnapshot::of and prove a stuck wait aborts rather than hangs. Docs in lib.rs + README updated off the frame-count model. All 61 scenarios still pass.Bundles pre-existing in-progress changes committed at the user's request (no gates run): - docs/features/ckeditor-search.md — project-search feature design. - command_bar.rs, keys.rs, state/keybindings/{mod,store}.rs — search command + keybinding wiring. - project/buffers.rs, project/mod.rs — supporting buffer/project changes. - help/pages/commands.md — command help updates. - ckeditor-lint/baseline.toml + baseline_CHANGELOG.md — baseline updates. - docs/tickets/ — ck-lsp rust-analyzer timeout flake; frozen-window / no-quit-command bug reports.The world-context-reflection commit (edb6ea0) landed --no-verify and explicitly deferred the CK022 LOC ratchet on five at-ceiling files. This resolves that deferral by continuing the god-class split — extraction, not ceiling-raising — and ratchets every improvement down. project/ submodule splits: - error.rs — ProjectError enum + Display/Error impls (from mod.rs) - lock.rs — flock(2) ProjectLock + retry (from persistence.rs; LOCK_RETRY_*/ERR_FLOCK_* moved out of mod.rs) - terminals.rs — active-context terminal accessors (from buffers.rs) - worktree_ops.rs— remove/pin/name git ops (from worktree.rs), sharing a new canon_or() that dedups 7 canonicalize-or-fallback copies app_layout/ split: - key_dispatch.rs— keybinding dispatch free functions (from keys.rs) CK022 [files] ratchet-downs: project/mod.rs 1906->1883, buffers.rs 537->505, persistence.rs 388->350, worktree.rs 624->586, app_layout/keys.rs 542->420. No baseline value raised; module-level coupling unchanged (submodules add no top-level edges). CK018 allow-list: added lock.rs + worktree_ops.rs (direct-fs code moved from already-allowed files); narrowed worktree.rs justification. Gates: lint 0/0, build 0 warnings, 291 project lib tests + 61/61 non-environmental scenarios green. Committed --no-verify (user-authorized): the lone hook failure is the MCP CLI health scenario requiring local `claude` server approval — environmental, unrelated to this diff.Clicking checkout on the root branch (master) from a worktree's branch list emitted Execute(Checkout), which git refuses because master is checked out in the main worktree — the action errored instead of switching. checkout_action_for now routes the default/root branch to the existing back-to-main switch (SwitchWorktree("")). Extracted the pure button-decision helpers (checkout_action_for, delete_action_for) out of git_panel.rs — at its CK022 LOC ceiling — into a new sibling module panels/git_panel_actions.rs, where the fix lives and is unit-tested directly (7 tests, no &mut Ui). Ratchets git_panel.rs 1066 -> 1045; new-module baseline updates documented in the changelog.Replace the transient project-search modal with a persistent content-area search buffer: live grouped results with case-sensitive and whole-word toggles, arrow/Ctrl-J/K navigation, Enter-to-jump, and a replace field whose Replace-all routes through files_coordinator::apply_edits (open buffers update in-place, closed files write through the self-write ledger). - New panels/search_buffer/{engine,render,mod} module; engine carries 9 unit tests (case folding, word boundaries, char-column ranges). - app_layout/search_actions.rs holds the app-side wiring as free functions to keep CkEditorApp off the LCOM/RFC baselines. - Triggers: Ctrl+F (new Action::ProjectSearch), :search, and the repointed // in the search bar. Ctrl+Shift+F stays FormatDocument. - Delete panels/project_search.rs and its modal wiring (no duplicate surface). - baseline.toml: new module seeded, app_layout/mod.rs ratcheted down after the modal removal; minimal new-module wiring raises recorded in baseline_CHANGELOG.md.Single-instance enforcement was invisible: launching CkEditor on a repo already open in another window logged a lock-refusal and left a dead window. Now the picker grays out and refuses already-open projects, and a lock-refusal at startup drops the user on the picker (that repo grayed) instead of a dead window. Two windows on different repos still work — the flock is per-project and unchanged. - project::is_locked — non-disruptive flock(LOCK_SH|LOCK_NB) probe that never creates state, never blocks, never disturbs the holder. The real exclusive gate in open_exclusive is untouched; this is an advisory UI hint. Lives in a new project/open_state.rs to keep lock.rs/persistence.rs flat. - RecentProjectsPicker carries a locked set computed once on open; locked rows render muted with a lock icon and are non-pickable (enter_pick gate, no click sense). Split into recent_projects/{mod,logic,render}.rs to stay under the per-file LOC ceiling. - session_restore::restore_or_offer_picker routes a startup lock-refusal to the picker (free function, off the CkEditorApp CC baseline). Type metrics held flat (enter_pick/probe as free functions). baseline.toml LOC raises for the residual wiring recorded in baseline_CHANGELOG.md with user sign-off (category 3); recent_projects extracted rather than raised.