Skip to content

feat: skip unchanged defs via CstNode physical_equal#36

Merged
dowdiness merged 1 commit intomainfrom
feature/flatproj-opt
Mar 18, 2026
Merged

feat: skip unchanged defs via CstNode physical_equal#36
dowdiness merged 1 commit intomainfrom
feature/flatproj-opt

Conversation

@dowdiness
Copy link
Owner

@dowdiness dowdiness commented Mar 18, 2026

Summary

Add to_flat_proj_incremental that compares old and new CST children via CstNode equality (physical_equal fast-path from NodeInterner). Unchanged LetDefs reuse old FlatProj entries directly — skipping both syntax_to_proj_node and reconcile_ast.

How it works

Three memo paths:

  • Incremental: prev FlatProj + prev SyntaxNode available → compare CstNode pointers, reuse unchanged entries
  • Reconcile-only: prev FlatProj but no prev SyntaxNode (after tree edit) → full rebuild + reconcile for ID preservation
  • Fresh: no prev FlatProj → fresh build

Performance impact

Benefits incremental editing (single keystroke changes) where most CST children are unchanged. Full reparse benchmarks are unaffected since every child is new.

Test plan

  • 232/232 tests passing
  • Tree edit bridge tests pass (ID preservation after tree edit + reparse)
  • seed_flat_proj clears prev syntax root to avoid stale comparisons

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Incremental projection rebuild that reuses unchanged items and reconciles edits to preserve identities.
  • Performance

    • Faster, more responsive incremental projection updates.
    • Added benchmarks measuring incremental keystroke performance for medium and large scenarios.
  • Bug Fixes

    • Improved handling of prior syntax state with a fallback to full rebuild when needed; seeding now forces full rebuild to avoid stale state.

@chatgpt-codex-connector
Copy link

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai
Copy link

coderabbitai bot commented Mar 18, 2026

Warning

Rate limit exceeded

@dowdiness has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 1 minutes and 27 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: acf3d8ea-f080-42cf-ac2b-c674d512c865

📥 Commits

Reviewing files that changed from the base of the PR and between e452f1d and 9a86f5a.

📒 Files selected for processing (5)
  • editor/performance_benchmark.mbt
  • editor/projection_memo.mbt
  • editor/sync_editor.mbt
  • projection/flat_proj.mbt
  • projection/pkg.generated.mbti
📝 Walkthrough

Walkthrough

Adds incremental flat-projection support and plumbing: introduces to_flat_proj_incremental and reconcile_flat_proj, threads a new prev_syntax_root_ref through build_projection_memos and SyncEditor, and selects incremental or fallback projection paths based on prior syntax root and flat-proj state.

Changes

Cohort / File(s) Summary
Incremental Flat Projection Logic
projection/flat_proj.mbt, projection/pkg.generated.mbti
Add to_flat_proj_incremental(new_root, old_root, old_fp, counter) for incremental FlatProj construction and reconcile_flat_proj(old_fp, new_fp, counter) to preserve IDs when reconciling; expose both in public API.
Projection Memo Integration
editor/projection_memo.mbt
Add prev_syntax_root_ref : Ref[\@seam.SyntaxNode?] parameter to build_projection_memos; use incremental path when previous syntax root and prev flat-proj exist, else build fresh flat-proj and reconcile; update memo storage and ensure seeding clears prev_syntax_root_ref.
SyncEditor Plumbing
editor/sync_editor.mbt
Add private field prev_syntax_root: Ref[\@seam.SyntaxNode?], initialize in SyncEditor::new(), pass into build_projection_memos() and include in the returned SyncEditor instance.
Benchmarks
editor/performance_benchmark.mbt
Add run_projection_incremental_bench and two tests ("projection pipeline - incremental keystroke (20 defs)" and "(80 defs)") to exercise incremental projection recompute on keystrokes.

Sequence Diagram(s)

sequenceDiagram
    participant SyncEditor as SyncEditor
    participant Memo as build_projection_memos
    participant Inc as to_flat_proj_incremental
    participant Fresh as to_flat_proj
    participant Reconcile as reconcile_flat_proj

    SyncEditor->>Memo: call(prev_flat_proj_ref, prev_syntax_root_ref, syntax_root, counter)
    alt prev_syntax_root exists AND prev_flat_proj exists
        Memo->>Inc: (syntax_root, prev_syntax_root, prev_flat_proj, counter)
        Inc-->>Memo: incremental FlatProj (reused IDs)
    else prev_syntax_root unavailable
        Memo->>Fresh: (syntax_root, counter)
        Fresh-->>Memo: fresh FlatProj
        Memo->>Reconcile: (prev_flat_proj, fresh_flat_proj, counter)
        Reconcile-->>Memo: reconciled FlatProj (preserves IDs)
    end
    Memo->>SyncEditor: store prev_flat_proj_ref and update prev_syntax_root_ref
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

Poem

🐇 I hop across syntax, old roots and new,

I stitch the IDs that you once knew.
Small edits I keep, big changes I remake,
nibble diffs gently — no extra cake.
Hooray for incremental carrots! 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly reflects the core optimization: skipping unchanged definitions by leveraging CstNode physical equality, which is the primary technical improvement in this changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/flatproj-opt
📝 Coding Plan
  • Generate coding plan for human review comments

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 and usage tips.

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Mar 18, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
lambda-editor 9a86f5a Commit Preview URL

Branch Preview URL
Mar 18 2026, 10:16 AM

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
projection/flat_proj.mbt (1)

64-71: Potential double reconciliation when entry is reused unchanged.

When a LetDef is unchanged (line 67 reuses old_fp.defs[old_def_idx] directly), line 119 still calls reconcile_flat_proj which will invoke reconcile_ast on this already-unchanged entry. Since the old and new ProjNodes are identical, reconcile_ast will return the old node (no-op), but the hash lookups and traversal still occur.

Consider tracking which entries were reused to skip reconciliation for them, or accept this as acceptable overhead for code simplicity.

💡 Sketch for avoiding redundant reconciliation

One approach: build new_fp with a marker (e.g., sentinel NodeId or separate tracking array) for reused entries, then skip them in reconcile_flat_proj. This adds complexity, so the current approach may be acceptable if the incremental path is already providing sufficient speedup.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projection/flat_proj.mbt` around lines 64 - 71, The reuse branch for LetDef
entries directly pushes old_fp.defs[old_def_idx] but later reconcile_flat_proj
still processes that index, causing redundant reconciliation; to fix, record
which entries were reused (e.g., add a reusable boolean vector or mark with a
sentinel NodeId when pushing in the LetDef reuse branch where
old_fp.defs[old_def_idx] is pushed) and then, inside reconcile_flat_proj (and
any loop that calls reconcile_ast), skip calling reconcile_ast for indices
flagged as reused so unchanged entries are not revisited during reconciliation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@projection/flat_proj.mbt`:
- Around line 64-71: The reuse branch for LetDef entries directly pushes
old_fp.defs[old_def_idx] but later reconcile_flat_proj still processes that
index, causing redundant reconciliation; to fix, record which entries were
reused (e.g., add a reusable boolean vector or mark with a sentinel NodeId when
pushing in the LetDef reuse branch where old_fp.defs[old_def_idx] is pushed) and
then, inside reconcile_flat_proj (and any loop that calls reconcile_ast), skip
calling reconcile_ast for indices flagged as reused so unchanged entries are not
revisited during reconciliation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 98e0c8a7-644e-482b-9179-ce8a6e4c2ba5

📥 Commits

Reviewing files that changed from the base of the PR and between e92d2d0 and d678387.

📒 Files selected for processing (4)
  • editor/projection_memo.mbt
  • editor/sync_editor.mbt
  • projection/flat_proj.mbt
  • projection/pkg.generated.mbti

@github-actions
Copy link

github-actions bot commented Mar 18, 2026

Benchmark Comparison Report

Comparing PR branch against main

Main Module Benchmarks

Base branch:

 WARN Duplicate alias `svg` at "/home/runner/work/crdt/crdt/graphviz/src/lib/svg/moon.pkg". "test-import" will automatically add "import" and current package as dependency so you don't need to add it manually. If you're test-importing a dependency with the same default alias as your current package, considering give it a different alias than the current package. Violating import: `antisatori/svg-dsl/lib`
[dowdiness/crdt] bench editor/performance_benchmark.mbt:33 ("parser benchmark - reactive full reparse medium") ok
time (mean ± σ)         range (min … max) 
 238.73 µs ±  17.23 µs   221.32 µs … 271.07 µs  in 10 ×    384 runs
[dowdiness/crdt] bench editor/performance_benchmark.mbt:54 ("parser benchmark - imperative incremental medium") ok
time (mean ± σ)         range (min … max) 
 246.13 µs ±   9.43 µs   233.93 µs … 263.12 µs  in 10 ×    412 runs
[dowdiness/crdt] bench editor/performance_benchmark.mbt:79 ("parser benchmark - reactive full reparse large") ok
time (mean ± σ)         range (min … max) 
   1.06 ms ±  53.68 µs     1.01 ms …   1.18 ms  in 10 ×     92 runs
[dowdiness/crdt] bench editor/performance_benchmark.mbt:100 ("parser benchmark - imperative incremental large") ok
time (mean ± σ)         range (min … max) 
   1.00 ms ±   6.14 µs   991.33 µs …   1.01 ms  in 10 ×     99 runs
Total tests: 4, passed: 4, failed: 0.

PR branch:

Downloading moonbitlang/quickcheck@0.9.10
Downloading moonbitlang/x@0.4.38
 WARN Duplicate alias `svg` at "/home/runner/work/crdt/crdt/graphviz/src/lib/svg/moon.pkg". "test-import" will automatically add "import" and current package as dependency so you don't need to add it manually. If you're test-importing a dependency with the same default alias as your current package, considering give it a different alias than the current package. Violating import: `antisatori/svg-dsl/lib`
[dowdiness/crdt] bench editor/performance_benchmark.mbt:27 ("projection pipeline - incremental keystroke (20 defs)") ok
time (mean ± σ)         range (min … max) 
   1.30 ms ± 293.65 µs   918.95 µs …   1.72 ms  in 10 ×    139 runs
[dowdiness/crdt] bench editor/performance_benchmark.mbt:32 ("projection pipeline - incremental keystroke (80 defs)") ok
time (mean ± σ)         range (min … max) 
   2.22 ms ± 117.63 µs     2.06 ms …   2.38 ms  in 10 ×     50 runs
[dowdiness/crdt] bench editor/performance_benchmark.mbt:70 ("parser benchmark - reactive full reparse medium") ok
time (mean ± σ)         range (min … max) 
 259.26 µs ±   6.17 µs   252.73 µs … 268.47 µs  in 10 ×    381 runs
[dowdiness/crdt] bench editor/performance_benchmark.mbt:91 ("parser benchmark - imperative incremental medium") ok
time (mean ± σ)         range (min … max) 
 254.99 µs ±   1.47 µs   252.84 µs … 256.82 µs  in 10 ×    394 runs
[dowdiness/crdt] bench editor/performance_benchmark.mbt:116 ("parser benchmark - reactive full reparse large") ok
time (mean ± σ)         range (min … max) 
   1.04 ms ±  18.47 µs     1.02 ms …   1.07 ms  in 10 ×     95 runs
[dowdiness/crdt] bench editor/performance_benchmark.mbt:137 ("parser benchmark - imperative incremental large") ok
time (mean ± σ)         range (min … max) 
   1.06 ms ±   9.32 µs     1.05 ms …   1.07 ms  in 10 ×     94 runs
Total tests: 6, passed: 6, failed: 0.

event-graph-walker Benchmarks

Base branch:

[dowdiness/event-graph-walker] bench internal/branch/branch_merge_benchmark.mbt:21 ("merge - concurrent edits (2 agents x 10)") ok
time (mean ± σ)         range (min … max) 
  32.17 µs ±   0.78 µs    31.72 µs …  34.35 µs  in 10 ×   2907 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_merge_benchmark.mbt:59 ("merge - concurrent edits (2 agents x 50)") ok
time (mean ± σ)         range (min … max) 
 211.05 µs ±   1.69 µs   208.45 µs … 213.84 µs  in 10 ×    469 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_merge_benchmark.mbt:97 ("merge - concurrent edits (2 agents x 200)") ok
time (mean ± σ)         range (min … max) 
   1.04 ms ±   6.72 µs     1.02 ms …   1.05 ms  in 10 ×     96 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_merge_benchmark.mbt:135 ("merge - many agents (5 agents x 20)") ok
time (mean ± σ)         range (min … max) 
 302.92 µs ±   2.68 µs   299.84 µs … 307.90 µs  in 10 ×    328 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_merge_benchmark.mbt:184 ("merge - with deletes (50 inserts, 25 deletes)") ok
time (mean ± σ)         range (min … max) 
 172.40 µs ±   1.03 µs   171.12 µs … 173.71 µs  in 10 ×    582 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_merge_benchmark.mbt:230 ("merge - graph_diff advance (20 ops)") ok
time (mean ± σ)         range (min … max) 
 122.52 µs ±   1.19 µs   121.32 µs … 125.26 µs  in 10 ×    810 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_merge_benchmark.mbt:303 ("merge - repeated small (10 iterations x 5 ops)") ok
time (mean ± σ)         range (min … max) 
 265.50 µs ±   2.32 µs   261.93 µs … 268.63 µs  in 10 ×    377 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_merge_benchmark.mbt:341 ("merge - context apply operations (50 ops)") ok
time (mean ± σ)         range (min … max) 
  17.86 µs ±   0.10 µs    17.72 µs …  18.02 µs  in 10 ×   5573 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:6 ("branch - checkout (10 ops)") ok
time (mean ± σ)         range (min … max) 
   7.16 µs ±   0.10 µs     7.10 µs …   7.42 µs  in 10 ×  13908 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:29 ("branch - checkout (100 ops)") ok
time (mean ± σ)         range (min … max) 
 115.77 µs ±   1.17 µs   114.67 µs … 118.35 µs  in 10 ×    870 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:47 ("branch - checkout (1000 ops)") ok
time (mean ± σ)         range (min … max) 
   2.06 ms ±  67.74 µs     2.02 ms …   2.21 ms  in 10 ×     47 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:65 ("branch - advance (10 new ops)") ok
time (mean ± σ)         range (min … max) 
  39.67 µs ±   0.20 µs    39.48 µs …  40.15 µs  in 10 ×   2520 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:91 ("branch - advance (100 new ops)") ok
time (mean ± σ)         range (min … max) 
 170.04 µs ±   0.84 µs   168.79 µs … 171.58 µs  in 10 ×    579 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:117 ("branch - checkout with concurrent branches") ok
time (mean ± σ)         range (min … max) 
 119.42 µs ±   0.47 µs   118.75 µs … 120.16 µs  in 10 ×    842 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:145 ("branch - checkout with deletes") ok
time (mean ± σ)         range (min … max) 
 182.97 µs ±   1.14 µs   180.79 µs … 183.80 µs  in 10 ×    540 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:176 ("branch - repeated advance steady-state (10 iterations)") ok
time (mean ± σ)         range (min … max) 
 137.73 µs ±   0.35 µs   137.34 µs … 138.29 µs  in 10 ×    724 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:220 ("branch - repeated advance with oplog mutations (10 iterations)") ok
time (mean ± σ)         range (min … max) 
  21.85 ms ±  10.37 ms     7.69 ms …  36.61 ms  in 10 ×     31 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:246 ("branch - to_text (100 chars)") ok
time (mean ± σ)         range (min … max) 
  17.68 µs ±   0.17 µs    17.44 µs …  17.87 µs  in 10 ×   5777 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:264 ("branch - to_text (1000 chars)") ok
time (mean ± σ)         range (min … max) 
 273.79 µs ±   1.49 µs   271.24 µs … 276.18 µs  in 10 ×    364 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:285 ("branch - single advance (1 new op)") ok
time (mean ± σ)         range (min … max) 
  27.33 µs ±   0.07 µs    27.23 µs …  27.45 µs  in 10 ×   3641 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:311 ("branch - single advance (50 new ops)") ok
time (mean ± σ)         range (min … max) 
  92.44 µs ±   0.95 µs    91.19 µs …  93.80 µs  in 10 ×   1079 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:342 ("branch - realistic typing (50 chars)") ok
time (mean ± σ)         range (min … max) 
  82.88 ms ±  20.70 ms    54.19 ms … 110.89 ms  in 10 ×      4 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:375 ("branch - concurrent merge scenario") ok
time (mean ± σ)         range (min … max) 
  27.17 µs ±   0.10 µs    27.06 µs …  27.36 µs  in 10 ×   3691 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/walker_benchmark.mbt:6 ("walker - linear history (10 ops)") ok
time (mean ± σ)         range (min … max) 
   4.71 µs ±   0.05 µs     4.62 µs …   4.78 µs  in 10 ×  21070 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/walker_benchmark.mbt:22 ("walker - linear history (100 ops)") ok
time (mean ± σ)         range (min … max) 
  79.21 µs ±   1.36 µs    78.11 µs …  82.93 µs  in 10 ×   1215 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/walker_benchmark.mbt:38 ("walker - linear history (1000 ops)") ok
time (mean ± σ)         range (min … max) 
   1.40 ms ±  55.51 µs     1.36 ms …   1.51 ms  in 10 ×     69 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/walker_benchmark.mbt:54 ("walker - concurrent branches (2 agents x 50)") ok
time (mean ± σ)         range (min … max) 
  78.32 µs ±   0.16 µs    78.09 µs …  78.59 µs  in 10 ×   1273 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/walker_benchmark.mbt:79 ("walker - concurrent branches (5 agents x 20)") ok
time (mean ± σ)         range (min … max) 
  78.07 µs ±   0.20 µs    77.75 µs …  78.42 µs  in 10 ×   1279 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/walker_benchmark.mbt:99 ("walker - diamond pattern (50 diamonds)") ok
time (mean ± σ)         range (min … max) 
 150.14 µs ±   0.86 µs   149.04 µs … 151.33 µs  in 10 ×    675 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/walker_benchmark.mbt:121 ("walker - diff advance only (10 new ops)") ok
time (mean ± σ)         range (min … max) 
  32.14 µs ±   0.09 µs    32.02 µs …  32.30 µs  in 10 ×   3098 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/walker_benchmark.mbt:145 ("walker - diff with concurrent branches") ok
time (mean ± σ)         range (min … max) 
  62.60 µs ±   0.28 µs    62.23 µs …  63.02 µs  in 10 ×   1602 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/walker_benchmark.mbt:176 ("walker - large history (10000 ops)") ok
time (mean ± σ)         range (min … max) 
  21.67 ms ± 699.68 µs    20.93 ms …  22.81 ms  in 10 ×      5 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/walker_benchmark.mbt:196 ("walker - linear history (100000 ops)") ok
time (mean ± σ)         range (min … max) 
 683.29 ms ±  55.35 ms   600.05 ms … 745.41 ms  in 10 ×      1 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/walker_benchmark.mbt:213 ("walker - concurrent branches (100000 ops, 5 agents)") ok
time (mean ± σ)         range (min … max) 
 729.68 ms ±  73.83 ms   612.43 ms … 849.63 ms  in 10 ×      1 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/version_vector_benchmark.mbt:6 ("version_vector - create (1 agent)") ok
time (mean ± σ)         range (min … max) 
   0.06 µs ±   0.00 µs     0.06 µs …   0.06 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/version_vector_benchmark.mbt:15 ("version_vector - create (5 agents)") ok
time (mean ± σ)         range (min … max) 
   0.32 µs ±   0.00 µs     0.32 µs …   0.32 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/version_vector_benchmark.mbt:29 ("version_vector - create (20 agents)") ok
time (mean ± σ)         range (min … max) 
   1.51 µs ±   0.01 µs     1.51 µs …   1.53 µs  in 10 ×  66192 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/version_vector_benchmark.mbt:42 ("version_vector - compare equal (5 agents)") ok
time (mean ± σ)         range (min … max) 
   0.26 µs ±   0.00 µs     0.26 µs …   0.26 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/version_vector_benchmark.mbt:63 ("version_vector - compare <= (5 agents)") ok
time (mean ± σ)         range (min … max) 
   0.15 µs ±   0.00 µs     0.15 µs …   0.15 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/version_vector_benchmark.mbt:84 ("version_vector - compare <= (20 agents)") ok
time (mean ± σ)         range (min … max) 
   0.62 µs ±   0.01 µs     0.61 µs …   0.63 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/version_vector_benchmark.mbt:100 ("version_vector - merge (5 agents)") ok
time (mean ± σ)         range (min … max) 
   0.48 µs ±   0.00 µs     0.47 µs …   0.48 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/version_vector_benchmark.mbt:118 ("version_vector - merge (20 agents)") ok
time (mean ± σ)         range (min … max) 
   2.39 µs ±   0.01 µs     2.38 µs …   2.40 µs  in 10 ×  41735 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/version_vector_benchmark.mbt:134 ("version_vector - includes (5 agents)") ok
time (mean ± σ)         range (min … max) 
   0.24 µs ±   0.00 µs     0.24 µs …   0.24 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/version_vector_benchmark.mbt:153 ("version_vector - concurrent (5 agents)") ok
time (mean ± σ)         range (min … max) 
   0.15 µs ±   0.00 µs     0.15 µs …   0.16 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/version_vector_benchmark.mbt:170 ("version_vector - from_frontier (10 ops)") ok
time (mean ± σ)         range (min … max) 
   1.31 µs ±   0.00 µs     1.31 µs …   1.31 µs  in 10 ×  76235 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/version_vector_benchmark.mbt:186 ("version_vector - from_frontier (100 ops, 5 agents)") ok
time (mean ± σ)         range (min … max) 
  17.98 µs ±   0.06 µs    17.91 µs …  18.10 µs  in 10 ×   5586 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/version_vector_benchmark.mbt:206 ("version_vector - to_frontier (5 agents)") ok
time (mean ± σ)         range (min … max) 
   0.23 µs ±   0.00 µs     0.22 µs …   0.23 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/version_vector_benchmark.mbt:230 ("version_vector - roundtrip (5 agents)") ok
time (mean ± σ)         range (min … max) 
  18.33 µs ±   0.05 µs    18.24 µs …  18.40 µs  in 10 ×   5436 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/version_vector_benchmark.mbt:251 ("version_vector - agents (5 agents)") ok
time (mean ± σ)         range (min … max) 
   0.05 µs ±   0.00 µs     0.05 µs …   0.05 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/version_vector_benchmark.mbt:266 ("version_vector - length (20 agents)") ok
time (mean ± σ)         range (min … max) 
   0.01 µs ±   0.00 µs     0.01 µs …   0.01 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:6 ("oplog - insert (100 ops)") ok
time (mean ± σ)         range (min … max) 
  64.26 µs ±   1.49 µs    63.33 µs …  68.16 µs  in 10 ×   1535 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:20 ("oplog - insert (1000 ops)") ok
time (mean ± σ)         range (min … max) 
 812.74 µs ±   4.01 µs   806.29 µs … 819.32 µs  in 10 ×    124 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:34 ("oplog - insert and delete mix (100 ops)") ok
time (mean ± σ)         range (min … max) 
  97.27 µs ±   0.29 µs    96.88 µs …  97.85 µs  in 10 ×   1030 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:58 ("oplog - apply_remote (50 ops)") ok
time (mean ± σ)         range (min … max) 
  38.08 µs ±   0.11 µs    37.96 µs …  38.28 µs  in 10 ×   2634 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:79 ("oplog - get_op (1000 ops)") ok
time (mean ± σ)         range (min … max) 
   0.16 µs ±   0.00 µs     0.15 µs …   0.16 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:97 ("oplog - get_frontier (single agent)") ok
time (mean ± σ)         range (min … max) 
   0.02 µs ±   0.00 µs     0.02 µs …   0.02 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:112 ("oplog - get_frontier (5 agents)") ok
time (mean ± σ)         range (min … max) 
   0.02 µs ±   0.00 µs     0.02 µs …   0.03 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:134 ("oplog - walk_and_collect (100 ops)") ok
time (mean ± σ)         range (min … max) 
  88.60 µs ±   0.60 µs    87.67 µs …  89.39 µs  in 10 ×   1124 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:150 ("oplog - walk_and_collect (concurrent)") ok
time (mean ± σ)         range (min … max) 
  91.75 µs ±   0.79 µs    90.53 µs …  93.18 µs  in 10 ×   1084 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:177 ("oplog - diff_and_collect (advance 20)") ok
time (mean ± σ)         range (min … max) 
  43.06 µs ±   0.78 µs    42.36 µs …  44.85 µs  in 10 ×   2337 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:202 ("oplog - walk_filtered (inserts only)") ok
time (mean ± σ)         range (min … max) 
  63.10 µs ±   0.38 µs    62.47 µs …  63.58 µs  in 10 ×   1581 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:233 ("oplog - sequential typing (500 chars)") ok
time (mean ± σ)         range (min … max) 
 364.44 µs ±   0.93 µs   363.00 µs … 365.90 µs  in 10 ×    274 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:249 ("oplog - random position inserts (100 ops)") ok
time (mean ± σ)         range (min … max) 
  63.79 µs ±   0.18 µs    63.55 µs …  64.06 µs  in 10 ×   1574 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:270 ("oplog - sequential typing (100000 chars)") ok
time (mean ± σ)         range (min … max) 
 254.19 ms ±  39.85 ms   200.87 ms … 306.99 ms  in 10 ×      1 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:287 ("oplog - diff_and_collect (100000 ops advance)") ok
time (mean ± σ)         range (min … max) 
 746.40 ms ±  33.49 ms   705.95 ms … 812.28 ms  in 10 ×      1 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:10 ("text - insert append (100 chars)") ok
time (mean ± σ)         range (min … max) 
  99.09 µs ±   2.53 µs    97.48 µs … 105.81 µs  in 10 ×    982 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:22 ("text - insert append (1000 chars)") ok
time (mean ± σ)         range (min … max) 
   1.37 ms ±  29.41 µs     1.34 ms …   1.42 ms  in 10 ×     73 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:35 ("text - insert prepend (100 chars)") ok
time (mean ± σ)         range (min … max) 
   1.22 ms ±   4.28 µs     1.22 ms …   1.23 ms  in 10 ×     81 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:51 ("text - delete (100 deletes from 100-char doc)") ok
time (mean ± σ)         range (min … max) 
   2.38 ms ±  25.17 µs     2.35 ms …   2.43 ms  in 10 ×     42 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:70 ("text - text() (100-char doc)") ok
time (mean ± σ)         range (min … max) 
  19.09 µs ±   0.05 µs    19.03 µs …  19.20 µs  in 10 ×   5183 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:83 ("text - text() (1000-char doc)") ok
time (mean ± σ)         range (min … max) 
 267.56 µs ±   2.11 µs   264.86 µs … 270.63 µs  in 10 ×    378 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:96 ("text - len() (1000-char doc)") ok
time (mean ± σ)         range (min … max) 
   0.01 µs ±   0.00 µs     0.01 µs …   0.01 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:113 ("text - sync export_all (100 ops)") ok
time (mean ± σ)         range (min … max) 
   0.11 µs ±   0.00 µs     0.11 µs …   0.11 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:126 ("text - sync export_all (1000 ops)") ok
time (mean ± σ)         range (min … max) 
   0.11 µs ±   0.00 µs     0.11 µs …   0.12 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:139 ("text - sync export_since (50-op delta, 1000-op base)") ok
time (mean ± σ)         range (min … max) 
 559.52 µs ±   6.70 µs   552.32 µs … 570.13 µs  in 10 ×    178 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:156 ("text - sync apply (50 remote ops)") ok
time (mean ± σ)         range (min … max) 
 106.03 µs ±   0.69 µs   104.95 µs … 107.11 µs  in 10 ×    915 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:171 ("text - sync apply (500 remote ops)") ok
time (mean ± σ)         range (min … max) 
   1.64 ms ±  39.13 µs     1.61 ms …   1.70 ms  in 10 ×     62 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:186 ("text - bidirectional sync (2 peers, 50 ops each)") ok
time (mean ± σ)         range (min … max) 
 222.42 µs ±   4.42 µs   219.07 µs … 233.65 µs  in 10 ×    457 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:216 ("text - checkout (midpoint of 100-op doc)") ok
time (mean ± σ)         range (min … max) 
  53.67 µs ±   0.29 µs    53.19 µs …  54.00 µs  in 10 ×   1875 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:233 ("text - checkout (midpoint of 1000-op doc)") ok
time (mean ± σ)         range (min … max) 
 919.08 µs ±  15.02 µs   902.07 µs … 949.46 µs  in 10 ×    106 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:256 ("text - undo record_insert (100 ops, 1 group)") ok
time (mean ± σ)         range (min … max) 
   2.09 µs ±   0.01 µs     2.08 µs …   2.12 µs  in 10 ×  48029 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:269 ("text - undo record_insert (100 ops, 100 groups)") ok
time (mean ± σ)         range (min … max) 
   2.44 µs ±   0.02 µs     2.42 µs …   2.47 µs  in 10 ×  40737 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:281 ("text - undo undo() (10-op group)") ok
time (mean ± σ)         range (min … max) 
  32.21 µs ±   0.10 µs    32.09 µs …  32.38 µs  in 10 ×   3089 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:295 ("text - undo undo() (50-op group)") ok
time (mean ± σ)         range (min … max) 
 600.58 µs ±   1.60 µs   597.07 µs … 602.45 µs  in 10 ×    167 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:309 ("text - undo undo+redo roundtrip (10-op group)") ok
time (mean ± σ)         range (min … max) 
  38.82 µs ±   0.06 µs    38.75 µs …  38.93 µs  in 10 ×   2579 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:324 ("text - undo 10 undo+redo cycles (10-op group)") ok
time (mean ± σ)         range (min … max) 
 352.68 µs ±   2.60 µs   350.65 µs … 358.83 µs  in 10 ×    285 runs
Total tests: 86, passed: 86, failed: 0.

PR branch:

Downloading moonbitlang/quickcheck@0.9.9
[dowdiness/event-graph-walker] bench internal/branch/branch_merge_benchmark.mbt:21 ("merge - concurrent edits (2 agents x 10)") ok
time (mean ± σ)         range (min … max) 
  32.52 µs ±   0.68 µs    31.89 µs …  34.08 µs  in 10 ×   2911 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_merge_benchmark.mbt:59 ("merge - concurrent edits (2 agents x 50)") ok
time (mean ± σ)         range (min … max) 
 211.26 µs ±   2.94 µs   208.64 µs … 218.32 µs  in 10 ×    455 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_merge_benchmark.mbt:97 ("merge - concurrent edits (2 agents x 200)") ok
time (mean ± σ)         range (min … max) 
   1.06 ms ±  20.61 µs     1.03 ms …   1.09 ms  in 10 ×     97 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_merge_benchmark.mbt:135 ("merge - many agents (5 agents x 20)") ok
time (mean ± σ)         range (min … max) 
 301.67 µs ±   2.37 µs   298.91 µs … 305.39 µs  in 10 ×    328 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_merge_benchmark.mbt:184 ("merge - with deletes (50 inserts, 25 deletes)") ok
time (mean ± σ)         range (min … max) 
 176.96 µs ±   1.34 µs   174.24 µs … 178.64 µs  in 10 ×    575 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_merge_benchmark.mbt:230 ("merge - graph_diff advance (20 ops)") ok
time (mean ± σ)         range (min … max) 
 123.71 µs ±   0.41 µs   122.98 µs … 124.31 µs  in 10 ×    803 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_merge_benchmark.mbt:303 ("merge - repeated small (10 iterations x 5 ops)") ok
time (mean ± σ)         range (min … max) 
 266.11 µs ±   0.70 µs   264.98 µs … 267.18 µs  in 10 ×    375 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_merge_benchmark.mbt:341 ("merge - context apply operations (50 ops)") ok
time (mean ± σ)         range (min … max) 
  17.56 µs ±   0.11 µs    17.43 µs …  17.74 µs  in 10 ×   5739 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:6 ("branch - checkout (10 ops)") ok
time (mean ± σ)         range (min … max) 
   7.35 µs ±   0.05 µs     7.30 µs …   7.48 µs  in 10 ×  13550 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:29 ("branch - checkout (100 ops)") ok
time (mean ± σ)         range (min … max) 
 119.77 µs ±   2.82 µs   117.05 µs … 126.50 µs  in 10 ×    847 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:47 ("branch - checkout (1000 ops)") ok
time (mean ± σ)         range (min … max) 
   2.22 ms ±  40.61 µs     2.16 ms …   2.28 ms  in 10 ×     45 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:65 ("branch - advance (10 new ops)") ok
time (mean ± σ)         range (min … max) 
  38.57 µs ±   0.13 µs    38.42 µs …  38.81 µs  in 10 ×   2554 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:91 ("branch - advance (100 new ops)") ok
time (mean ± σ)         range (min … max) 
 178.83 µs ±   3.30 µs   174.34 µs … 184.62 µs  in 10 ×    562 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:117 ("branch - checkout with concurrent branches") ok
time (mean ± σ)         range (min … max) 
 124.65 µs ±   0.60 µs   124.00 µs … 125.76 µs  in 10 ×    813 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:145 ("branch - checkout with deletes") ok
time (mean ± σ)         range (min … max) 
 189.16 µs ±   1.76 µs   187.28 µs … 192.35 µs  in 10 ×    534 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:176 ("branch - repeated advance steady-state (10 iterations)") ok
time (mean ± σ)         range (min … max) 
 137.83 µs ±   1.28 µs   136.05 µs … 140.46 µs  in 10 ×    731 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:220 ("branch - repeated advance with oplog mutations (10 iterations)") ok
time (mean ± σ)         range (min … max) 
  18.50 ms ±   4.46 ms    12.42 ms …  24.68 ms  in 10 ×     14 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:246 ("branch - to_text (100 chars)") ok
time (mean ± σ)         range (min … max) 
  17.50 µs ±   0.19 µs    17.37 µs …  18.01 µs  in 10 ×   5740 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:264 ("branch - to_text (1000 chars)") ok
time (mean ± σ)         range (min … max) 
 261.29 µs ±   1.55 µs   259.70 µs … 263.99 µs  in 10 ×    378 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:285 ("branch - single advance (1 new op)") ok
time (mean ± σ)         range (min … max) 
  27.01 µs ±   0.10 µs    26.88 µs …  27.18 µs  in 10 ×   3711 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:311 ("branch - single advance (50 new ops)") ok
time (mean ± σ)         range (min … max) 
  93.99 µs ±   0.48 µs    93.43 µs …  94.84 µs  in 10 ×   1050 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:342 ("branch - realistic typing (50 chars)") ok
time (mean ± σ)         range (min … max) 
  84.87 ms ±  22.12 ms    53.32 ms … 115.50 ms  in 10 ×      4 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:375 ("branch - concurrent merge scenario") ok
time (mean ± σ)         range (min … max) 
  27.70 µs ±   0.08 µs    27.58 µs …  27.81 µs  in 10 ×   3629 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/walker_benchmark.mbt:6 ("walker - linear history (10 ops)") ok
time (mean ± σ)         range (min … max) 
   4.79 µs ±   0.06 µs     4.69 µs …   4.86 µs  in 10 ×  20608 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/walker_benchmark.mbt:22 ("walker - linear history (100 ops)") ok
time (mean ± σ)         range (min … max) 
  80.22 µs ±   1.43 µs    79.58 µs …  84.29 µs  in 10 ×   1202 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/walker_benchmark.mbt:38 ("walker - linear history (1000 ops)") ok
time (mean ± σ)         range (min … max) 
   1.42 ms ±   8.60 µs     1.41 ms …   1.44 ms  in 10 ×     71 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/walker_benchmark.mbt:54 ("walker - concurrent branches (2 agents x 50)") ok
time (mean ± σ)         range (min … max) 
  80.18 µs ±   0.16 µs    79.93 µs …  80.43 µs  in 10 ×   1252 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/walker_benchmark.mbt:79 ("walker - concurrent branches (5 agents x 20)") ok
time (mean ± σ)         range (min … max) 
  79.31 µs ±   0.26 µs    78.90 µs …  79.66 µs  in 10 ×   1264 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/walker_benchmark.mbt:99 ("walker - diamond pattern (50 diamonds)") ok
time (mean ± σ)         range (min … max) 
 152.98 µs ±   0.33 µs   152.56 µs … 153.48 µs  in 10 ×    652 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/walker_benchmark.mbt:121 ("walker - diff advance only (10 new ops)") ok
time (mean ± σ)         range (min … max) 
  32.60 µs ±   0.10 µs    32.46 µs …  32.71 µs  in 10 ×   3050 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/walker_benchmark.mbt:145 ("walker - diff with concurrent branches") ok
time (mean ± σ)         range (min … max) 
  63.98 µs ±   0.11 µs    63.80 µs …  64.12 µs  in 10 ×   1563 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/walker_benchmark.mbt:176 ("walker - large history (10000 ops)") ok
time (mean ± σ)         range (min … max) 
  22.61 ms ± 987.27 µs    21.51 ms …  23.89 ms  in 10 ×      4 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/walker_benchmark.mbt:196 ("walker - linear history (100000 ops)") ok
time (mean ± σ)         range (min … max) 
 657.32 ms ±  69.48 ms   577.82 ms … 770.40 ms  in 10 ×      1 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/walker_benchmark.mbt:213 ("walker - concurrent branches (100000 ops, 5 agents)") ok
time (mean ± σ)         range (min … max) 
 729.82 ms ±  68.34 ms   638.94 ms … 857.45 ms  in 10 ×      1 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/version_vector_benchmark.mbt:6 ("version_vector - create (1 agent)") ok
time (mean ± σ)         range (min … max) 
   0.06 µs ±   0.00 µs     0.06 µs …   0.06 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/version_vector_benchmark.mbt:15 ("version_vector - create (5 agents)") ok
time (mean ± σ)         range (min … max) 
   0.33 µs ±   0.00 µs     0.32 µs …   0.33 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/version_vector_benchmark.mbt:29 ("version_vector - create (20 agents)") ok
time (mean ± σ)         range (min … max) 
   1.53 µs ±   0.00 µs     1.52 µs …   1.53 µs  in 10 ×  65392 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/version_vector_benchmark.mbt:42 ("version_vector - compare equal (5 agents)") ok
time (mean ± σ)         range (min … max) 
   0.26 µs ±   0.00 µs     0.26 µs …   0.26 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/version_vector_benchmark.mbt:63 ("version_vector - compare <= (5 agents)") ok
time (mean ± σ)         range (min … max) 
   0.15 µs ±   0.00 µs     0.15 µs …   0.15 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/version_vector_benchmark.mbt:84 ("version_vector - compare <= (20 agents)") ok
time (mean ± σ)         range (min … max) 
   0.62 µs ±   0.00 µs     0.62 µs …   0.62 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/version_vector_benchmark.mbt:100 ("version_vector - merge (5 agents)") ok
time (mean ± σ)         range (min … max) 
   0.47 µs ±   0.00 µs     0.47 µs …   0.47 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/version_vector_benchmark.mbt:118 ("version_vector - merge (20 agents)") ok
time (mean ± σ)         range (min … max) 
   2.38 µs ±   0.01 µs     2.37 µs …   2.39 µs  in 10 ×  42280 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/version_vector_benchmark.mbt:134 ("version_vector - includes (5 agents)") ok
time (mean ± σ)         range (min … max) 
   0.24 µs ±   0.00 µs     0.24 µs …   0.24 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/version_vector_benchmark.mbt:153 ("version_vector - concurrent (5 agents)") ok
time (mean ± σ)         range (min … max) 
   0.15 µs ±   0.00 µs     0.15 µs …   0.15 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/version_vector_benchmark.mbt:170 ("version_vector - from_frontier (10 ops)") ok
time (mean ± σ)         range (min … max) 
   1.31 µs ±   0.00 µs     1.30 µs …   1.31 µs  in 10 ×  76463 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/version_vector_benchmark.mbt:186 ("version_vector - from_frontier (100 ops, 5 agents)") ok
time (mean ± σ)         range (min … max) 
  18.30 µs ±   0.35 µs    17.95 µs …  19.17 µs  in 10 ×   5426 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/version_vector_benchmark.mbt:206 ("version_vector - to_frontier (5 agents)") ok
time (mean ± σ)         range (min … max) 
   0.22 µs ±   0.00 µs     0.22 µs …   0.23 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/version_vector_benchmark.mbt:230 ("version_vector - roundtrip (5 agents)") ok
time (mean ± σ)         range (min … max) 
  18.45 µs ±   0.05 µs    18.38 µs …  18.52 µs  in 10 ×   5406 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/version_vector_benchmark.mbt:251 ("version_vector - agents (5 agents)") ok
time (mean ± σ)         range (min … max) 
   0.05 µs ±   0.00 µs     0.05 µs …   0.05 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/version_vector_benchmark.mbt:266 ("version_vector - length (20 agents)") ok
time (mean ± σ)         range (min … max) 
   0.01 µs ±   0.00 µs     0.01 µs …   0.01 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:6 ("oplog - insert (100 ops)") ok
time (mean ± σ)         range (min … max) 
  65.04 µs ±   1.46 µs    64.04 µs …  68.76 µs  in 10 ×   1532 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:20 ("oplog - insert (1000 ops)") ok
time (mean ± σ)         range (min … max) 
 839.65 µs ±  10.97 µs   820.36 µs … 852.41 µs  in 10 ×    118 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:34 ("oplog - insert and delete mix (100 ops)") ok
time (mean ± σ)         range (min … max) 
  97.95 µs ±   0.36 µs    97.50 µs …  98.60 µs  in 10 ×   1023 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:58 ("oplog - apply_remote (50 ops)") ok
time (mean ± σ)         range (min … max) 
  38.05 µs ±   0.10 µs    37.91 µs …  38.20 µs  in 10 ×   2629 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:79 ("oplog - get_op (1000 ops)") ok
time (mean ± σ)         range (min … max) 
   0.15 µs ±   0.00 µs     0.15 µs …   0.15 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:97 ("oplog - get_frontier (single agent)") ok
time (mean ± σ)         range (min … max) 
   0.02 µs ±   0.00 µs     0.02 µs …   0.02 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:112 ("oplog - get_frontier (5 agents)") ok
time (mean ± σ)         range (min … max) 
   0.02 µs ±   0.00 µs     0.02 µs …   0.03 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:134 ("oplog - walk_and_collect (100 ops)") ok
time (mean ± σ)         range (min … max) 
  89.06 µs ±   0.58 µs    88.18 µs …  89.87 µs  in 10 ×   1121 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:150 ("oplog - walk_and_collect (concurrent)") ok
time (mean ± σ)         range (min … max) 
  91.80 µs ±   0.86 µs    90.70 µs …  92.77 µs  in 10 ×   1082 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:177 ("oplog - diff_and_collect (advance 20)") ok
time (mean ± σ)         range (min … max) 
  43.16 µs ±   0.08 µs    43.01 µs …  43.29 µs  in 10 ×   2310 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:202 ("oplog - walk_filtered (inserts only)") ok
time (mean ± σ)         range (min … max) 
  63.23 µs ±   0.16 µs    63.00 µs …  63.48 µs  in 10 ×   1588 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:233 ("oplog - sequential typing (500 chars)") ok
time (mean ± σ)         range (min … max) 
 368.88 µs ±   2.68 µs   365.46 µs … 372.39 µs  in 10 ×    272 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:249 ("oplog - random position inserts (100 ops)") ok
time (mean ± σ)         range (min … max) 
  63.56 µs ±   0.22 µs    63.36 µs …  64.12 µs  in 10 ×   1569 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:270 ("oplog - sequential typing (100000 chars)") ok
time (mean ± σ)         range (min … max) 
 276.94 ms ±  34.92 ms   216.71 ms … 309.46 ms  in 10 ×      1 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:287 ("oplog - diff_and_collect (100000 ops advance)") ok
time (mean ± σ)         range (min … max) 
 794.94 ms ±  45.50 ms   730.99 ms … 847.24 ms  in 10 ×      1 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:10 ("text - insert append (100 chars)") ok
time (mean ± σ)         range (min … max) 
 100.64 µs ±   2.23 µs    99.36 µs … 106.90 µs  in 10 ×    974 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:22 ("text - insert append (1000 chars)") ok
time (mean ± σ)         range (min … max) 
   1.41 ms ±  26.87 µs     1.37 ms …   1.45 ms  in 10 ×     72 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:35 ("text - insert prepend (100 chars)") ok
time (mean ± σ)         range (min … max) 
   1.21 ms ±   8.66 µs     1.20 ms …   1.22 ms  in 10 ×     81 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:51 ("text - delete (100 deletes from 100-char doc)") ok
time (mean ± σ)         range (min … max) 
   2.39 ms ±  11.37 µs     2.38 ms …   2.41 ms  in 10 ×     42 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:70 ("text - text() (100-char doc)") ok
time (mean ± σ)         range (min … max) 
  19.49 µs ±   0.10 µs    19.34 µs …  19.59 µs  in 10 ×   5102 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:83 ("text - text() (1000-char doc)") ok
time (mean ± σ)         range (min … max) 
 270.38 µs ±   2.63 µs   267.96 µs … 274.08 µs  in 10 ×    372 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:96 ("text - len() (1000-char doc)") ok
time (mean ± σ)         range (min … max) 
   0.01 µs ±   0.00 µs     0.01 µs …   0.01 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:113 ("text - sync export_all (100 ops)") ok
time (mean ± σ)         range (min … max) 
   0.11 µs ±   0.00 µs     0.11 µs …   0.11 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:126 ("text - sync export_all (1000 ops)") ok
time (mean ± σ)         range (min … max) 
   0.11 µs ±   0.00 µs     0.11 µs …   0.11 µs  in 10 × 100000 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:139 ("text - sync export_since (50-op delta, 1000-op base)") ok
time (mean ± σ)         range (min … max) 
 568.11 µs ±  12.59 µs   551.10 µs … 585.46 µs  in 10 ×    180 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:156 ("text - sync apply (50 remote ops)") ok
time (mean ± σ)         range (min … max) 
 105.92 µs ±   0.73 µs   104.71 µs … 106.84 µs  in 10 ×    930 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:171 ("text - sync apply (500 remote ops)") ok
time (mean ± σ)         range (min … max) 
   1.62 ms ±  18.59 µs     1.58 ms …   1.65 ms  in 10 ×     63 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:186 ("text - bidirectional sync (2 peers, 50 ops each)") ok
time (mean ± σ)         range (min … max) 
 219.19 µs ±   0.93 µs   218.01 µs … 220.56 µs  in 10 ×    456 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:216 ("text - checkout (midpoint of 100-op doc)") ok
time (mean ± σ)         range (min … max) 
  53.30 µs ±   0.10 µs    53.19 µs …  53.55 µs  in 10 ×   1881 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:233 ("text - checkout (midpoint of 1000-op doc)") ok
time (mean ± σ)         range (min … max) 
 928.25 µs ±  17.17 µs   908.12 µs … 959.44 µs  in 10 ×    109 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:256 ("text - undo record_insert (100 ops, 1 group)") ok
time (mean ± σ)         range (min … max) 
   2.11 µs ±   0.01 µs     2.10 µs …   2.11 µs  in 10 ×  47179 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:269 ("text - undo record_insert (100 ops, 100 groups)") ok
time (mean ± σ)         range (min … max) 
   2.45 µs ±   0.02 µs     2.43 µs …   2.48 µs  in 10 ×  40966 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:281 ("text - undo undo() (10-op group)") ok
time (mean ± σ)         range (min … max) 
  32.71 µs ±   0.10 µs    32.53 µs …  32.89 µs  in 10 ×   3050 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:295 ("text - undo undo() (50-op group)") ok
time (mean ± σ)         range (min … max) 
 613.05 µs ±   4.22 µs   607.48 µs … 619.35 µs  in 10 ×    165 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:309 ("text - undo undo+redo roundtrip (10-op group)") ok
time (mean ± σ)         range (min … max) 
  39.32 µs ±   0.17 µs    39.07 µs …  39.55 µs  in 10 ×   2528 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:324 ("text - undo 10 undo+redo cycles (10-op group)") ok
time (mean ± σ)         range (min … max) 
 357.73 µs ±   0.95 µs   356.61 µs … 359.14 µs  in 10 ×    279 runs
Total tests: 86, passed: 86, failed: 0.

Benchmarks run with --release flag

@dowdiness dowdiness force-pushed the feature/flatproj-opt branch from d678387 to 129a960 Compare March 18, 2026 09:15
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
editor/performance_benchmark.mbt (1)

5-47: Consider extracting a shared benchmark helper.

The 20-def and 80-def tests are structurally identical except for let_count; a small helper would reduce drift risk.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@editor/performance_benchmark.mbt` around lines 5 - 47, Extract a shared
helper to avoid duplication between the two tests by moving the common setup and
benchmarking logic into a function like
run_projection_incremental_bench(let_count: Int, name: String) that calls
parser_bench_source(let_count, "0"), creates SyncEditor::new(name), sets text,
warms with get_proj_node(), positions the cursor using source.length() - 2, and
runs the b.bench closure that toggles insert/backspace and calls
b.keep(editor.get_proj_node()); then replace both test "projection pipeline -
incremental keystroke (20 defs)" and test "projection pipeline - incremental
keystroke (80 defs)" to call this helper with 20 and 80 respectively (preserve
the existing editor.insert/backspace behavior and error handling).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@editor/performance_benchmark.mbt`:
- Around line 14-24: Replace the anonymous MoonBit callback functions currently
written using the legacy `fn() { ... }` form with arrow function syntax `() => {
... }` for the benchmark callbacks; locate the two `b.bench(fn() { ... })`
usages (the blocks that toggle `inserted`, call `editor.backspace()` or
`editor.insert("1")`, and call `b.keep(editor.get_proj_node())`) and rewrite
each callback as `() => { ... }` so they follow the MoonBit guideline.

In `@projection/flat_proj.mbt`:
- Around line 64-67: The reuse of old projection entries (when
new_child.cst_node() == old_child.cst_node() and
defs.push(old_fp.defs[old_def_idx])) preserves stale start() offsets and
corrupts source ranges after edits; instead, when reusing an entry reconstruct
or update its source-range fields from the current CST node (use
new_child.cst_node().start() / relevant new_child positions) or build a fresh
projection tuple for that node so the start()/end() offsets reflect the new AST
positions; update both reuse sites (the block around new_child.cst_node() ==
old_child.cst_node() and the similar block at lines 104-106) to derive offsets
from the new_child rather than copying the old tuple wholesale.

---

Nitpick comments:
In `@editor/performance_benchmark.mbt`:
- Around line 5-47: Extract a shared helper to avoid duplication between the two
tests by moving the common setup and benchmarking logic into a function like
run_projection_incremental_bench(let_count: Int, name: String) that calls
parser_bench_source(let_count, "0"), creates SyncEditor::new(name), sets text,
warms with get_proj_node(), positions the cursor using source.length() - 2, and
runs the b.bench closure that toggles insert/backspace and calls
b.keep(editor.get_proj_node()); then replace both test "projection pipeline -
incremental keystroke (20 defs)" and test "projection pipeline - incremental
keystroke (80 defs)" to call this helper with 20 and 80 respectively (preserve
the existing editor.insert/backspace behavior and error handling).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dc604c2d-b589-4a76-be61-147ef0e48fa3

📥 Commits

Reviewing files that changed from the base of the PR and between d678387 and 129a960.

📒 Files selected for processing (5)
  • editor/performance_benchmark.mbt
  • editor/projection_memo.mbt
  • editor/sync_editor.mbt
  • projection/flat_proj.mbt
  • projection/pkg.generated.mbti
🚧 Files skipped from review as they are similar to previous changes (1)
  • editor/sync_editor.mbt

@dowdiness dowdiness force-pushed the feature/flatproj-opt branch from 129a960 to 637da9b Compare March 18, 2026 09:28
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
projection/flat_proj.mbt (1)

117-119: Consider whether reconciliation is needed for fully-reused entries.

When all entries were reused via CST pointer equality (line 67), reconcile_flat_proj is still called unconditionally. For entries that were already reused unchanged, the reconciliation:

  1. Matches by name (finds the same entry)
  2. Calls reconcile_ast(old_entry.1, new_init, counter) where both are the same ProjNode

This is safe but performs redundant work. For fully-incremental scenarios where most entries are unchanged, this adds unnecessary overhead.

💡 Potential optimization

Track whether any entries were actually rebuilt, and skip reconciliation when all were reused:

+  let mut any_rebuilt = false
   for new_child in new_children {
     if `@parser.LetDefView`::cast(new_child) is Some(v) {
       // ... existing logic ...
       if not(reused) {
+        any_rebuilt = true
         // Changed or new: build fresh
         // ... existing logic ...
       }
     } else if final_proj is None {
       // ... existing logic ...
       if not(reused_final) {
+        any_rebuilt = true
         final_proj = Some(syntax_to_proj_node(new_child, counter))
       }
     }
   }
   let new_fp : FlatProj = { defs, final_expr: final_proj }
-  reconcile_flat_proj(old_fp, new_fp, counter)
+  if any_rebuilt {
+    reconcile_flat_proj(old_fp, new_fp, counter)
+  } else {
+    new_fp
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projection/flat_proj.mbt` around lines 117 - 119, The code always calls
reconcile_flat_proj(old_fp, new_fp, counter) even when every entry was reused
via CST pointer equality, causing redundant work; modify the builder that
creates new_fp (the defs/final_expr construction around where pointer-equality
checks occur) to track a boolean flag like any_rebuilt which is set true
whenever you actually allocate/replace a ProjNode or rebuild an entry, and after
creating let new_fp : FlatProj = { defs, final_expr: final_proj } only call
reconcile_flat_proj(old_fp, new_fp, counter) when any_rebuilt is true; if no
entries were rebuilt, skip reconciliation (or simply reuse old_fp/new_fp
identity) to avoid the redundant reconcile_ast(old_entry.1, new_init, counter)
calls.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@projection/flat_proj.mbt`:
- Around line 117-119: The code always calls reconcile_flat_proj(old_fp, new_fp,
counter) even when every entry was reused via CST pointer equality, causing
redundant work; modify the builder that creates new_fp (the defs/final_expr
construction around where pointer-equality checks occur) to track a boolean flag
like any_rebuilt which is set true whenever you actually allocate/replace a
ProjNode or rebuild an entry, and after creating let new_fp : FlatProj = { defs,
final_expr: final_proj } only call reconcile_flat_proj(old_fp, new_fp, counter)
when any_rebuilt is true; if no entries were rebuilt, skip reconciliation (or
simply reuse old_fp/new_fp identity) to avoid the redundant
reconcile_ast(old_entry.1, new_init, counter) calls.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fdfa6566-b5c4-4b0c-85a8-c426bd23d9a2

📥 Commits

Reviewing files that changed from the base of the PR and between 129a960 and 637da9b.

📒 Files selected for processing (5)
  • editor/performance_benchmark.mbt
  • editor/projection_memo.mbt
  • editor/sync_editor.mbt
  • projection/flat_proj.mbt
  • projection/pkg.generated.mbti
🚧 Files skipped from review as they are similar to previous changes (1)
  • editor/performance_benchmark.mbt

@dowdiness dowdiness force-pushed the feature/flatproj-opt branch 2 times, most recently from e452f1d to f936bcf Compare March 18, 2026 10:01
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
projection/flat_proj.mbt (1)

64-71: ⚠️ Potential issue | 🟠 Major

Prevent stale source ranges when reusing old projection nodes.

Line 70 and Line 109 reuse old ProjNode values even though offsets may shift (as noted at Line 67). That preserves stale ranges in reused trees.

🛠️ Suggested guard to avoid unsafe reuse after positional shifts
-          if new_child.cst_node() == old_child.cst_node() &&
+          if new_child.cst_node() == old_child.cst_node() &&
+            new_child.start() == old_child.start() &&
             old_def_idx < old_fp.defs.length() {
-          if new_child.cst_node() == old_child.cst_node() {
+          if new_child.cst_node() == old_child.cst_node() &&
+            new_child.start() == old_child.start() {
             final_proj = old_fp.final_expr
             reused_final = true
           }

Also applies to: 108-109


ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3f098c67-d299-4807-bb95-6ede836ff949

📥 Commits

Reviewing files that changed from the base of the PR and between 637da9b and e452f1d.

📒 Files selected for processing (5)
  • editor/performance_benchmark.mbt
  • editor/projection_memo.mbt
  • editor/sync_editor.mbt
  • projection/flat_proj.mbt
  • projection/pkg.generated.mbti
🚧 Files skipped from review as they are similar to previous changes (1)
  • editor/sync_editor.mbt

@dowdiness dowdiness force-pushed the feature/flatproj-opt branch from f936bcf to aaa6b93 Compare March 18, 2026 10:11
Add to_flat_proj_incremental: compares old and new CST children via
CstNode equality (physical_equal fast-path from NodeInterner).
Unchanged LetDefs reuse old FlatProj entries directly — skipping
both syntax_to_proj_node and reconcile_ast.

Three memo paths:
- Incremental: prev FlatProj + prev SyntaxNode → physical_equal skip
- Reconcile-only: prev FlatProj, no prev SyntaxNode → full rebuild + reconcile
- Fresh: no prev FlatProj → fresh build

The benefit shows in incremental editing (single keystroke changes)
where most CST children are unchanged. Full reparse benchmarks are
unaffected since every child is new.
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.

1 participant