Skip to content

refactor: decompose compute_text_edit into handler chain with middleware#54

Merged
dowdiness merged 15 commits intomainfrom
worktree-handler-chain-refactor
Mar 23, 2026
Merged

refactor: decompose compute_text_edit into handler chain with middleware#54
dowdiness merged 15 commits intomainfrom
worktree-handler-chain-refactor

Conversation

@dowdiness
Copy link
Owner

@dowdiness dowdiness commented Mar 23, 2026

Summary

  • Decompose projection/text_edit.mbt (1,064 lines → 96 lines) into per-file handler functions with an exhaustive thin router
  • Introduce EditContext struct to bundle the 5 parameters of compute_text_edit
  • Add EditMiddleware trait and ValidateNodeExists middleware (SuperOOP-inspired composable chain)
  • Extract shared helpers: find_def_index (was duplicated 6x), binding_delete_range (was duplicated 3x)
  • Fix pre-existing bugs found during review:
    • Move binding scoping guards were over-restrictive (blocked valid swaps)
    • InlineDefinition cursor used pre-edit coordinates after binding deletion
    • Unwrap cursor pointed to invalidated pre-edit position
  • Add defensive validations: AddBinding validates Module target, InsertChild rejects negative index

New files

  • text_edit_commit.mbt, text_edit_delete.mbt, text_edit_drop.mbt
  • text_edit_wrap.mbt, text_edit_structural.mbt, text_edit_binding.mbt
  • text_edit_refactor.mbt, text_edit_middleware.mbt

Cost of adding a future operation

Step Change
Add enum variant 1 line in tree_lens.mbt
Add router arm 1 line in text_edit.mbt
Implement handler 1 new file (self-contained)
Add JSON parsing ~5 lines in tree_edit_json.mbt
Add UI state 1 line in tree_editor.mbt

Test plan

  • All 507 tests pass (moon check && moon test)
  • Pure refactor through Phase 3 (no behavior changes)
  • Phase 4 middleware adds 1 new test for node validation
  • Phase 5 adds 5 tests for bug fixes and defensive guards
  • Code review: reuse, quality, and efficiency audits passed

🤖 Generated with Claude Code

Design spec: docs/plans/2026-03-23-handler-chain-design.md

Summary by CodeRabbit

Release Notes

  • New Features
    • Added binding operations: delete, duplicate, reorder bindings up/down, and add new bindings to modules
    • Added refactoring tools: extract expressions to let bindings, inline definitions and usages
    • Enhanced structural transformations: wrap nodes in expressions, unwrap children, swap operands, modify operators
    • Introduced drop operation for moving code between locations
    • Added comprehensive commit and delete operations with smart placeholder handling
    • Implemented validation layer for edit operations to catch invalid node references

dowdiness and others added 13 commits March 23, 2026 16:19
Bundle the 5 parameters of compute_text_edit (source_text, source_map,
registry, flat_proj) into an EditContext struct. Add EditResult type alias.
Update all 54 test call sites, the AddBinding recursive call, and the
editor/tree_edit_bridge.mbt call site.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove the now-unnecessary `let { source_text, source_map, registry,
flat_proj } = ctx` from compute_text_edit since all arms delegate to
extracted handler functions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… comments

- Extract find_def_index and binding_delete_range helpers to text_edit_utils.mbt
- Replace 6 duplicated def-index-lookup patterns across binding/refactor/rename files
- Replace 3 duplicated delete-range 3-way branches with binding_delete_range
- Inline ValidateNodeExists middleware call, removing wrap closure allocation
- Remove unused wrap function and redundant dispatch comments

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@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 23, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

This PR refactors projection text-edit wiring: it introduces an EditContext and EditResult, replaces the monolithic compute_text_edit router with middleware-backed dispatch that delegates to many new per-operation compute_* helpers, and updates bridge call sites and tests to use the new context.

Changes

Cohort / File(s) Summary
Bridge
editor/tree_edit_bridge.mbt
Builds an EditContext (replacing separate args) and calls compute_text_edit(op, ctx) instead of passing multiple parameters.
API surface
projection/pkg.generated.mbti, projection/text_edit.mbt
Added pub(all) struct EditContext and pub type EditResult; changed compute_text_edit signature to (TreeEditOp, EditContext).
Dispatch & Middleware
projection/text_edit.mbt, projection/text_edit_middleware.mbt
Extracted core_dispatch, added EditMiddleware trait and ValidateNodeExists, and introduced extract_primary_node_id to validate ops before delegating.
New compute helpers — structural & wrap
projection/text_edit_structural.mbt, projection/text_edit_wrap.mbt
Added helpers: compute_unwrap, compute_swap_children, compute_change_operator, compute_wrap_lambda, compute_wrap_app, compute_wrap_if, compute_wrap_bop, compute_insert_child.
New compute helpers — delete / drop / bindings
projection/text_edit_delete.mbt, projection/text_edit_drop.mbt, projection/text_edit_binding.mbt
Added compute_delete, compute_drop, and binding ops: compute_delete_binding, compute_duplicate_binding, compute_move_binding_up, compute_move_binding_down, compute_add_binding.
Commit, rename, refactor helpers
projection/text_edit_commit.mbt, projection/text_edit_rename.mbt, projection/text_edit_refactor.mbt
Added compute_commit, compute_rename (and refactored rename helpers to accept EditContext), plus refactor helpers: compute_extract_to_let, compute_inline_definition, compute_inline_all_usages.
Utilities & tests
projection/text_edit_utils.mbt, projection/text_edit_wbtest.mbt
Added find_def_index and binding_delete_range; test helper make_edit_ctx and updated tests to use EditContext.
Docs
docs/plans/2026-03-23-handler-chain-design.md
New design doc describing the middleware + per-op handler refactor and migration plan.

Sequence Diagram(s)

sequenceDiagram
    participant Editor as Editor (apply_tree_edit)
    participant API as compute_text_edit(op, ctx)
    participant Middleware as ValidateNodeExists (middleware)
    participant Dispatcher as core_dispatch / compute_* helpers
    participant Registry as ctx.registry / source_map

    Editor->>API: call(op, EditContext)
    activate API
    API->>Middleware: ValidateNodeExists.apply(op, ctx)
    activate Middleware
    Middleware->>Middleware: extract_primary_node_id(op)
    alt primary id present
        Middleware->>Registry: ctx.registry.contains(id)?
        alt exists
            Middleware->>Dispatcher: forward(op, ctx)
            activate Dispatcher
            Dispatcher->>Registry: lookup nodes / ranges
            Dispatcher->>Dispatcher: compute SpanEdit(s) + FocusHint
            Dispatcher-->>API: Ok(Some((edits, hint)))
            deactivate Dispatcher
        else missing
            Middleware-->>API: Err("Node not found: <id>")
        end
    else no primary id
        Middleware->>Dispatcher: forward(op, ctx)
        activate Dispatcher
        Dispatcher->>Registry: lookup as needed
        Dispatcher-->>API: Ok(Some((edits, hint)))
        deactivate Dispatcher
    end
    deactivate Middleware
    API-->>Editor: EditResult
    deactivate API
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I bundled inputs into one cozy sack,

I hop through middleware to check the track,
Helpers nibble bits and stitch the text anew,
Cursor hops back where the edit flew,
Thump-thump—projectional gardens wake, hip-hop hooray!

🚥 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 accurately summarizes the main refactor: decomposing compute_text_edit into separate handler functions with a middleware layer. This is the primary architectural change across the 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 worktree-handler-chain-refactor

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

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
rabbita 56bc095 Commit Preview URL

Branch Preview URL
Mar 23 2026, 08:08 AM

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Mar 23, 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 57e198a Commit Preview URL

Branch Preview URL
Mar 23 2026, 08:44 AM

@github-actions
Copy link

github-actions bot commented Mar 23, 2026

Benchmark Comparison Report

Comparing PR branch against main

Main Module Benchmarks

Base branch:

[dowdiness/canopy] bench editor/performance_benchmark.mbt:29 ("projection pipeline - incremental keystroke (20 defs)") ok
time (mean ± σ)         range (min … max) 
   1.28 ms ± 263.30 µs   909.54 µs …   1.67 ms  in 10 ×    138 runs
[dowdiness/canopy] bench editor/performance_benchmark.mbt:34 ("projection pipeline - incremental keystroke (80 defs)") ok
time (mean ± σ)         range (min … max) 
   2.10 ms ± 122.81 µs     1.96 ms …   2.28 ms  in 10 ×     53 runs
[dowdiness/canopy] bench editor/performance_benchmark.mbt:39 ("projection pipeline - incremental keystroke (320 defs)") ok
time (mean ± σ)         range (min … max) 
   8.56 ms ± 122.38 µs     8.37 ms …   8.72 ms  in 10 ×     12 runs
[dowdiness/canopy] bench editor/performance_benchmark.mbt:44 ("projection pipeline - incremental keystroke (500 defs)") ok
time (mean ± σ)         range (min … max) 
  14.39 ms ± 120.56 µs    14.21 ms …  14.56 ms  in 10 ×      8 runs
[dowdiness/canopy] bench editor/performance_benchmark.mbt:49 ("projection pipeline - incremental keystroke (1000 defs)") ok
time (mean ± σ)         range (min … max) 
  35.26 ms ±   2.82 ms    32.03 ms …  38.88 ms  in 10 ×      3 runs
[dowdiness/canopy] bench editor/performance_benchmark.mbt:87 ("parser benchmark - reactive full reparse medium") ok
time (mean ± σ)         range (min … max) 
 358.04 µs ±  10.98 µs   339.26 µs … 375.21 µs  in 10 ×    254 runs
[dowdiness/canopy] bench editor/performance_benchmark.mbt:108 ("parser benchmark - imperative incremental medium") ok
time (mean ± σ)         range (min … max) 
 137.01 µs ±   0.65 µs   136.13 µs … 138.10 µs  in 10 ×    719 runs
[dowdiness/canopy] bench editor/performance_benchmark.mbt:133 ("parser benchmark - reactive full reparse large") ok
time (mean ± σ)         range (min … max) 
   1.42 ms ±  40.49 µs     1.37 ms …   1.49 ms  in 10 ×     67 runs
[dowdiness/canopy] bench editor/performance_benchmark.mbt:154 ("parser benchmark - imperative incremental large") ok
time (mean ± σ)         range (min … max) 
 566.92 µs ±   4.55 µs   561.84 µs … 576.79 µs  in 10 ×    176 runs
[dowdiness/canopy] bench projection/tree_refresh_benchmark.mbt:32 ("tree refresh unchanged (20 defs)") ok
time (mean ± σ)         range (min … max) 
   5.19 µs ±   0.09 µs     5.12 µs …   5.35 µs  in 10 ×  18949 runs
[dowdiness/canopy] bench projection/tree_refresh_benchmark.mbt:42 ("tree refresh unchanged (80 defs)") ok
time (mean ± σ)         range (min … max) 
  19.60 µs ±   0.21 µs    19.45 µs …  20.15 µs  in 10 ×   5078 runs
[dowdiness/canopy] bench projection/tree_refresh_benchmark.mbt:52 ("tree refresh unchanged (320 defs)") ok
time (mean ± σ)         range (min … max) 
  83.32 µs ±   1.43 µs    82.14 µs …  87.03 µs  in 10 ×   1204 runs
[dowdiness/canopy] bench projection/tree_refresh_benchmark.mbt:62 ("tree refresh unchanged (1000 defs)") ok
time (mean ± σ)         range (min … max) 
 326.68 µs ±   1.19 µs   325.09 µs … 328.68 µs  in 10 ×    307 runs
[dowdiness/canopy] bench projection/tree_refresh_benchmark.mbt:74 ("tree refresh 1 changed (20 defs)") ok
time (mean ± σ)         range (min … max) 
   9.93 µs ±   0.04 µs     9.87 µs …  10.00 µs  in 10 ×  10066 runs
[dowdiness/canopy] bench projection/tree_refresh_benchmark.mbt:89 ("tree refresh 1 changed (320 defs)") ok
time (mean ± σ)         range (min … max) 
 168.19 µs ±   0.68 µs   167.32 µs … 169.30 µs  in 10 ×    587 runs
[dowdiness/canopy] bench projection/tree_refresh_benchmark.mbt:104 ("tree refresh 1 changed (1000 defs)") ok
time (mean ± σ)         range (min … max) 
 582.04 µs ±   7.10 µs   569.63 µs … 592.89 µs  in 10 ×    172 runs
[dowdiness/canopy] bench projection/tree_refresh_benchmark.mbt:119 ("tree refresh 1 changed (80 defs)") ok
time (mean ± σ)         range (min … max) 
  37.52 µs ±   0.06 µs    37.43 µs …  37.63 µs  in 10 ×   2671 runs
Total tests: 17, passed: 17, failed: 0.

PR branch:

Registry index cloned successfully
Symbols updated successfully
Downloading moonbitlang/quickcheck@0.9.10
Downloading moonbitlang/x@0.4.38
[dowdiness/canopy] bench editor/performance_benchmark.mbt:29 ("projection pipeline - incremental keystroke (20 defs)") ok
time (mean ± σ)         range (min … max) 
   1.24 ms ± 272.16 µs   868.60 µs …   1.65 ms  in 10 ×    142 runs
[dowdiness/canopy] bench editor/performance_benchmark.mbt:34 ("projection pipeline - incremental keystroke (80 defs)") ok
time (mean ± σ)         range (min … max) 
   2.02 ms ± 121.51 µs     1.86 ms …   2.18 ms  in 10 ×     56 runs
[dowdiness/canopy] bench editor/performance_benchmark.mbt:39 ("projection pipeline - incremental keystroke (320 defs)") ok
time (mean ± σ)         range (min … max) 
   8.29 ms ± 184.96 µs     8.09 ms …   8.67 ms  in 10 ×     13 runs
[dowdiness/canopy] bench editor/performance_benchmark.mbt:44 ("projection pipeline - incremental keystroke (500 defs)") ok
time (mean ± σ)         range (min … max) 
  14.84 ms ± 711.92 µs    13.95 ms …  16.27 ms  in 10 ×      8 runs
[dowdiness/canopy] bench editor/performance_benchmark.mbt:49 ("projection pipeline - incremental keystroke (1000 defs)") ok
time (mean ± σ)         range (min … max) 
  35.57 ms ±   1.20 ms    33.16 ms …  36.99 ms  in 10 ×      4 runs
[dowdiness/canopy] bench editor/performance_benchmark.mbt:87 ("parser benchmark - reactive full reparse medium") ok
time (mean ± σ)         range (min … max) 
 356.44 µs ±  11.00 µs   347.73 µs … 382.05 µs  in 10 ×    276 runs
[dowdiness/canopy] bench editor/performance_benchmark.mbt:108 ("parser benchmark - imperative incremental medium") ok
time (mean ± σ)         range (min … max) 
 135.13 µs ±   0.67 µs   134.37 µs … 136.42 µs  in 10 ×    721 runs
[dowdiness/canopy] bench editor/performance_benchmark.mbt:133 ("parser benchmark - reactive full reparse large") ok
time (mean ± σ)         range (min … max) 
   1.39 ms ±  30.87 µs     1.35 ms …   1.46 ms  in 10 ×     72 runs
[dowdiness/canopy] bench editor/performance_benchmark.mbt:154 ("parser benchmark - imperative incremental large") ok
time (mean ± σ)         range (min … max) 
 535.40 µs ±   3.10 µs   531.72 µs … 541.09 µs  in 10 ×    187 runs
[dowdiness/canopy] bench projection/tree_refresh_benchmark.mbt:32 ("tree refresh unchanged (20 defs)") ok
time (mean ± σ)         range (min … max) 
   5.14 µs ±   0.08 µs     5.07 µs …   5.28 µs  in 10 ×  19114 runs
[dowdiness/canopy] bench projection/tree_refresh_benchmark.mbt:42 ("tree refresh unchanged (80 defs)") ok
time (mean ± σ)         range (min … max) 
  19.83 µs ±   0.20 µs    19.61 µs …  20.33 µs  in 10 ×   4996 runs
[dowdiness/canopy] bench projection/tree_refresh_benchmark.mbt:52 ("tree refresh unchanged (320 defs)") ok
time (mean ± σ)         range (min … max) 
  80.77 µs ±   1.54 µs    79.80 µs …  84.62 µs  in 10 ×   1247 runs
[dowdiness/canopy] bench projection/tree_refresh_benchmark.mbt:62 ("tree refresh unchanged (1000 defs)") ok
time (mean ± σ)         range (min … max) 
 302.99 µs ±   1.32 µs   301.18 µs … 305.86 µs  in 10 ×    330 runs
[dowdiness/canopy] bench projection/tree_refresh_benchmark.mbt:74 ("tree refresh 1 changed (20 defs)") ok
time (mean ± σ)         range (min … max) 
   8.86 µs ±   0.02 µs     8.83 µs …   8.89 µs  in 10 ×  11296 runs
[dowdiness/canopy] bench projection/tree_refresh_benchmark.mbt:89 ("tree refresh 1 changed (320 defs)") ok
time (mean ± σ)         range (min … max) 
 147.26 µs ±   0.78 µs   146.38 µs … 148.51 µs  in 10 ×    677 runs
[dowdiness/canopy] bench projection/tree_refresh_benchmark.mbt:104 ("tree refresh 1 changed (1000 defs)") ok
time (mean ± σ)         range (min … max) 
 538.35 µs ±  12.11 µs   527.82 µs … 569.57 µs  in 10 ×    189 runs
[dowdiness/canopy] bench projection/tree_refresh_benchmark.mbt:119 ("tree refresh 1 changed (80 defs)") ok
time (mean ± σ)         range (min … max) 
  34.29 µs ±   0.22 µs    34.10 µs …  34.84 µs  in 10 ×   2913 runs
Total tests: 17, passed: 17, failed: 0.

event-graph-walker Benchmarks

Base branch:

Registry index updated successfully
Symbols updated successfully
[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.47 µs ±   0.64 µs    31.75 µs …  33.95 µs  in 10 ×   2938 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.17 µs ±   0.58 µs   210.30 µs … 211.86 µs  in 10 ×    472 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.01 ms ±   4.37 µs     1.01 ms …   1.02 ms  in 10 ×     98 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.42 µs ±   1.32 µs   299.57 µs … 303.64 µs  in 10 ×    333 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.60 µs ±   1.31 µs   171.15 µs … 174.90 µs  in 10 ×    576 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.91 µs ±   0.50 µs   122.40 µs … 123.73 µ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) 
 261.71 µs ±   1.84 µs   260.16 µs … 265.45 µ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.79 µs ±   0.11 µs    17.67 µs …  17.95 µs  in 10 ×   5590 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.02 µs     7.14 µs …   7.21 µs  in 10 ×  13926 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:29 ("branch - checkout (100 ops)") ok
time (mean ± σ)         range (min … max) 
 116.59 µs ±   0.75 µs   115.59 µs … 118.22 µs  in 10 ×    852 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:47 ("branch - checkout (1000 ops)") ok
time (mean ± σ)         range (min … max) 
   2.04 ms ±  19.58 µs     2.02 ms …   2.08 ms  in 10 ×     49 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:65 ("branch - advance (10 new ops)") ok
time (mean ± σ)         range (min … max) 
  37.22 µs ±   0.17 µs    37.02 µs …  37.49 µs  in 10 ×   2684 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:91 ("branch - advance (100 new ops)") ok
time (mean ± σ)         range (min … max) 
 167.85 µs ±   1.64 µs   166.46 µs … 171.04 µs  in 10 ×    587 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:117 ("branch - checkout with concurrent branches") ok
time (mean ± σ)         range (min … max) 
 118.88 µs ±   0.57 µs   118.01 µs … 119.68 µs  in 10 ×    841 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:145 ("branch - checkout with deletes") ok
time (mean ± σ)         range (min … max) 
 184.09 µs ±   1.20 µs   182.78 µs … 186.00 µs  in 10 ×    542 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.93 µs ±   4.27 µs   134.99 µs … 145.97 µs  in 10 ×    738 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) 
  20.91 ms ±   9.82 ms     7.42 ms …  35.65 ms  in 10 ×     32 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:246 ("branch - to_text (100 chars)") ok
time (mean ± σ)         range (min … max) 
  17.51 µs ±   0.04 µs    17.44 µs …  17.59 µs  in 10 ×   5691 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:264 ("branch - to_text (1000 chars)") ok
time (mean ± σ)         range (min … max) 
 263.40 µs ±   1.05 µs   262.39 µs … 265.62 µs  in 10 ×    377 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:285 ("branch - single advance (1 new op)") ok
time (mean ± σ)         range (min … max) 
  26.89 µs ±   0.19 µs    26.67 µs …  27.24 µs  in 10 ×   3740 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.09 µs ±   0.36 µs    91.64 µs …  92.72 µs  in 10 ×   1085 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:342 ("branch - realistic typing (50 chars)") ok
time (mean ± σ)         range (min … max) 
  78.67 ms ±  21.72 ms    49.60 ms … 108.25 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.13 µs ±   0.11 µs    26.97 µs …  27.26 µs  in 10 ×   3667 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.74 µs ±   0.06 µs     4.64 µs …   4.80 µs  in 10 ×  20972 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/walker_benchmark.mbt:22 ("walker - linear history (100 ops)") ok
time (mean ± σ)         range (min … max) 
  78.79 µs ±   1.49 µs    77.89 µs …  82.92 µs  in 10 ×   1223 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.38 ms ±  25.98 µs     1.34 ms …   1.42 ms  in 10 ×     72 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.70 µs ±   4.05 µs    78.10 µs …  88.29 µs  in 10 ×   1272 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) 
  77.34 µs ±   0.21 µs    77.11 µs …  77.70 µs  in 10 ×   1300 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/walker_benchmark.mbt:99 ("walker - diamond pattern (50 diamonds)") ok
time (mean ± σ)         range (min … max) 
 148.72 µs ±   0.42 µs   148.21 µs … 149.38 µs  in 10 ×    673 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.29 µs ±   0.11 µs    32.15 µs …  32.46 µs  in 10 ×   3082 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.66 µs ±   0.32 µs    62.35 µs …  63.38 µs  in 10 ×   1594 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.88 ms ± 629.25 µs    21.09 ms …  23.08 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) 
 614.69 ms ±  54.08 ms   547.69 ms … 693.37 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) 
 678.63 ms ±  66.55 ms   583.39 ms … 791.58 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.53 µs …   1.54 µs  in 10 ×  65485 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.64 µs ±   0.00 µs     0.63 µs …   0.64 µ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.41 µs ±   0.01 µs     2.39 µs …   2.43 µs  in 10 ×  41591 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.34 µs ±   0.08 µs     1.29 µs …   1.50 µs  in 10 ×  77114 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.16 µs ±   0.08 µs    18.08 µs …  18.34 µs  in 10 ×   5524 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.22 µ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.15 µs ±   0.05 µs    18.10 µs …  18.22 µs  in 10 ×   5504 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.98 µs ±   1.78 µs    63.66 µs …  69.53 µs  in 10 ×   1521 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:20 ("oplog - insert (1000 ops)") ok
time (mean ± σ)         range (min … max) 
 811.66 µs ±   3.28 µs   805.90 µs … 817.76 µs  in 10 ×    125 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) 
  99.40 µs ±   2.91 µs    97.57 µs … 106.11 µs  in 10 ×   1022 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:58 ("oplog - apply_remote (50 ops)") ok
time (mean ± σ)         range (min … max) 
  39.92 µs ±   1.21 µs    39.20 µs …  42.66 µs  in 10 ×   2555 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) 
  88.58 µs ±   0.51 µs    87.78 µs …  89.23 µs  in 10 ×   1135 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:150 ("oplog - walk_and_collect (concurrent)") ok
time (mean ± σ)         range (min … max) 
  91.29 µs ±   0.83 µs    90.19 µs …  93.03 µs  in 10 ×   1104 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:177 ("oplog - diff_and_collect (advance 20)") ok
time (mean ± σ)         range (min … max) 
  42.97 µs ±   1.02 µs    42.36 µs …  45.75 µs  in 10 ×   2353 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:202 ("oplog - walk_filtered (inserts only)") ok
time (mean ± σ)         range (min … max) 
  63.07 µs ±   0.12 µs    62.90 µs …  63.31 µs  in 10 ×   1587 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:233 ("oplog - sequential typing (500 chars)") ok
time (mean ± σ)         range (min … max) 
 365.75 µs ±   1.50 µs   363.98 µs … 368.36 µ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.71 µs ±   0.38 µs    63.34 µs …  64.63 µs  in 10 ×   1562 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:270 ("oplog - sequential typing (100000 chars)") ok
time (mean ± σ)         range (min … max) 
 251.84 ms ±  36.21 ms   197.37 ms … 289.25 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) 
 730.46 ms ±  19.72 ms   701.87 ms … 760.35 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.74 µs ±   2.15 µs    98.65 µs … 105.78 µs  in 10 ×    938 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:22 ("text - insert append (1000 chars)") ok
time (mean ± σ)         range (min … max) 
   1.34 ms ±   2.95 µs     1.34 ms …   1.35 ms  in 10 ×     75 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:35 ("text - insert prepend (100 chars)") ok
time (mean ± σ)         range (min … max) 
   1.19 ms ±   4.99 µs     1.18 ms …   1.20 ms  in 10 ×     83 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.28 ms ±  20.19 µs     2.26 ms …   2.32 ms  in 10 ×     44 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:70 ("text - text() (100-char doc)") ok
time (mean ± σ)         range (min … max) 
  18.25 µs ±   0.06 µs    18.14 µs …  18.32 µs  in 10 ×   5487 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:83 ("text - text() (1000-char doc)") ok
time (mean ± σ)         range (min … max) 
 267.55 µs ±   0.83 µs   266.57 µs … 269.28 µs  in 10 ×    374 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.01 µs     0.11 µs …   0.13 µ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) 
 566.99 µs ±   2.56 µs   563.87 µs … 570.96 µs  in 10 ×    174 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:156 ("text - sync apply (50 remote ops)") ok
time (mean ± σ)         range (min … max) 
 107.60 µs ±   1.05 µs   106.47 µs … 109.10 µs  in 10 ×    898 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:171 ("text - sync apply (500 remote ops)") ok
time (mean ± σ)         range (min … max) 
   1.57 ms ±   4.13 µs     1.56 ms …   1.57 ms  in 10 ×     64 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) 
 220.78 µs ±   1.10 µs   219.50 µs … 222.85 µs  in 10 ×    452 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:216 ("text - checkout (midpoint of 100-op doc)") ok
time (mean ± σ)         range (min … max) 
  54.28 µs ±   0.54 µs    53.81 µs …  55.44 µs  in 10 ×   1854 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:233 ("text - checkout (midpoint of 1000-op doc)") ok
time (mean ± σ)         range (min … max) 
 902.53 µs ±   5.99 µs   895.50 µs … 912.18 µ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.09 µs ±   0.00 µs     2.08 µs …   2.10 µs  in 10 ×  47921 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.42 µs ±   0.01 µs     2.41 µs …   2.45 µs  in 10 ×  41483 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:281 ("text - undo undo() (10-op group)") ok
time (mean ± σ)         range (min … max) 
  32.85 µs ±   0.24 µs    32.59 µs …  33.39 µs  in 10 ×   3021 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:295 ("text - undo undo() (50-op group)") ok
time (mean ± σ)         range (min … max) 
 585.68 µs ±   3.44 µs   581.40 µs … 592.05 µs  in 10 ×    171 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.51 µs ±   0.10 µs    39.38 µs …  39.67 µs  in 10 ×   2526 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) 
 364.99 µs ±  16.58 µs   356.58 µs … 407.64 µs  in 10 ×    258 runs
Total tests: 86, passed: 86, failed: 0.

PR branch:

Registry index updated successfully
Symbols updated successfully
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.39 µs ±   0.58 µs    31.97 µs …  33.91 µs  in 10 ×   2908 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) 
 213.78 µs ±   5.48 µs   209.54 µs … 225.32 µs  in 10 ×    471 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.03 ms ±   6.84 µs     1.02 ms …   1.04 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) 
 303.49 µs ±   0.84 µs   302.23 µs … 305.06 µs  in 10 ×    329 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) 
 171.58 µs ±   0.70 µs   170.48 µs … 172.68 µs  in 10 ×    579 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.45 µs ±   0.95 µs   121.99 µs … 125.44 µs  in 10 ×    811 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) 
 267.47 µs ±  10.65 µs   260.97 µs … 294.97 µs  in 10 ×    379 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) 
  18.25 µs ±   0.84 µs    17.75 µs …  20.27 µs  in 10 ×   5519 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:6 ("branch - checkout (10 ops)") ok
time (mean ± σ)         range (min … max) 
   7.26 µs ±   0.02 µs     7.24 µs …   7.29 µs  in 10 ×  13749 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:29 ("branch - checkout (100 ops)") ok
time (mean ± σ)         range (min … max) 
 117.24 µs ±   0.77 µs   116.25 µs … 118.37 µs  in 10 ×    865 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 ±  16.86 µs     2.04 ms …   2.09 ms  in 10 ×     50 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:65 ("branch - advance (10 new ops)") ok
time (mean ± σ)         range (min … max) 
  37.50 µs ±   0.16 µs    37.22 µs …  37.78 µs  in 10 ×   2657 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:91 ("branch - advance (100 new ops)") ok
time (mean ± σ)         range (min … max) 
 171.44 µs ±   1.69 µs   169.11 µs … 173.69 µs  in 10 ×    588 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:117 ("branch - checkout with concurrent branches") ok
time (mean ± σ)         range (min … max) 
 119.99 µs ±   0.67 µs   119.07 µs … 120.96 µs  in 10 ×    833 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:145 ("branch - checkout with deletes") ok
time (mean ± σ)         range (min … max) 
 185.96 µs ±   1.22 µs   184.62 µs … 188.65 µs  in 10 ×    543 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) 
 135.78 µs ±   0.33 µs   135.25 µs … 136.29 µs  in 10 ×    732 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.90 ms ±  10.56 ms     7.71 ms …  37.09 ms  in 10 ×     32 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:246 ("branch - to_text (100 chars)") ok
time (mean ± σ)         range (min … max) 
  17.41 µs ±   0.04 µs    17.36 µs …  17.49 µs  in 10 ×   5680 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:264 ("branch - to_text (1000 chars)") ok
time (mean ± σ)         range (min … max) 
 259.06 µs ±   1.11 µs   257.52 µs … 261.72 µs  in 10 ×    385 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:285 ("branch - single advance (1 new op)") ok
time (mean ± σ)         range (min … max) 
  26.82 µs ±   0.09 µs    26.70 µs …  27.04 µs  in 10 ×   3748 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.32 µs ±   1.09 µs    92.11 µs …  95.45 µs  in 10 ×   1063 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:342 ("branch - realistic typing (50 chars)") ok
time (mean ± σ)         range (min … max) 
  82.26 ms ±  21.69 ms    52.79 ms … 114.25 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.22 µs ±   0.08 µs    27.11 µs …  27.38 µs  in 10 ×   3663 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.74 µs ±   0.07 µs     4.65 µs …   4.83 µs  in 10 ×  20882 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/walker_benchmark.mbt:22 ("walker - linear history (100 ops)") ok
time (mean ± σ)         range (min … max) 
  78.58 µs ±   1.49 µs    77.95 µs …  82.80 µs  in 10 ×   1226 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.44 ms ±  29.19 µs     1.40 ms …   1.49 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) 
  79.00 µs ±   0.42 µs    78.47 µs …  79.79 µs  in 10 ×   1269 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) 
  77.70 µs ±   0.19 µs    77.46 µs …  78.01 µs  in 10 ×   1291 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/walker_benchmark.mbt:99 ("walker - diamond pattern (50 diamonds)") ok
time (mean ± σ)         range (min … max) 
 149.33 µs ±   0.52 µs   148.71 µs … 150.04 µs  in 10 ×    669 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.18 µs ±   0.43 µs    31.98 µs …  33.40 µs  in 10 ×   3093 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.33 µs ±   0.15 µs    62.17 µs …  62.58 µs  in 10 ×   1604 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.64 ms ±   1.05 ms    21.11 ms …  23.75 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) 
 607.27 ms ±  55.58 ms   539.94 ms … 678.24 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) 
 652.91 ms ±  47.38 ms   598.74 ms … 740.03 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.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.55 µs ±   0.00 µs     1.54 µs …   1.55 µs  in 10 ×  64674 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.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.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.36 µs ±   0.01 µs     2.35 µs …   2.38 µs  in 10 ×  42342 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.25 µs ±   0.00 µs     0.25 µs …   0.25 µ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.16 µ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.32 µs ±   0.01 µs     1.32 µs …   1.35 µs  in 10 ×  75236 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) 
  19.33 µs ±   0.05 µs    19.27 µs …  19.41 µs  in 10 ×   5165 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.22 µ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) 
  19.65 µs ±   0.05 µs    19.60 µs …  19.73 µs  in 10 ×   5087 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.17 µs ±   1.61 µs    63.06 µs …  68.10 µs  in 10 ×   1547 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:20 ("oplog - insert (1000 ops)") ok
time (mean ± σ)         range (min … max) 
 804.44 µs ±   3.75 µs   799.20 µs … 808.58 µ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.84 µs ±   0.22 µs    97.53 µs …  98.16 µs  in 10 ×   1022 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:58 ("oplog - apply_remote (50 ops)") ok
time (mean ± σ)         range (min … max) 
  38.80 µs ±   0.14 µs    38.63 µs …  39.02 µs  in 10 ×   2589 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.03 µ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.03 µs ±   0.00 µs     0.03 µ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.46 µs ±   0.39 µs    87.88 µs …  88.86 µs  in 10 ×   1133 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:150 ("oplog - walk_and_collect (concurrent)") ok
time (mean ± σ)         range (min … max) 
  91.04 µs ±   0.79 µs    89.71 µs …  91.82 µs  in 10 ×   1098 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:177 ("oplog - diff_and_collect (advance 20)") ok
time (mean ± σ)         range (min … max) 
  42.09 µs ±   0.22 µs    41.79 µs …  42.47 µs  in 10 ×   2376 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:202 ("oplog - walk_filtered (inserts only)") ok
time (mean ± σ)         range (min … max) 
  62.74 µs ±   0.36 µs    62.27 µs …  63.45 µs  in 10 ×   1595 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:233 ("oplog - sequential typing (500 chars)") ok
time (mean ± σ)         range (min … max) 
 363.44 µs ±   1.22 µs   361.63 µs … 365.09 µs  in 10 ×    277 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.03 µs ±   0.33 µs    62.65 µs …  63.47 µs  in 10 ×   1587 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:270 ("oplog - sequential typing (100000 chars)") ok
time (mean ± σ)         range (min … max) 
 250.58 ms ±  35.04 ms   196.66 ms … 289.69 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) 
 725.59 ms ±  22.51 ms   689.60 ms … 773.27 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) 
  98.39 µs ±   2.41 µs    97.10 µs … 105.05 µs  in 10 ×    950 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:22 ("text - insert append (1000 chars)") ok
time (mean ± σ)         range (min … max) 
   1.36 ms ±   9.39 µs     1.34 ms …   1.37 ms  in 10 ×     75 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:35 ("text - insert prepend (100 chars)") ok
time (mean ± σ)         range (min … max) 
   1.19 ms ±   6.57 µs     1.19 ms …   1.21 ms  in 10 ×     83 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.30 ms ±  20.00 µs     2.28 ms …   2.33 ms  in 10 ×     44 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:70 ("text - text() (100-char doc)") ok
time (mean ± σ)         range (min … max) 
  18.23 µs ±   0.07 µs    18.17 µs …  18.41 µs  in 10 ×   5494 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:83 ("text - text() (1000-char doc)") ok
time (mean ± σ)         range (min … max) 
 266.15 µs ±   1.12 µs   263.82 µs … 267.79 µs  in 10 ×    381 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) 
 564.13 µs ±   4.85 µs   559.58 µs … 571.80 µs  in 10 ×    176 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:156 ("text - sync apply (50 remote ops)") ok
time (mean ± σ)         range (min … max) 
 106.09 µs ±   1.57 µs   104.76 µs … 109.67 µ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 ±  55.88 µs     1.59 ms …   1.78 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) 
 217.34 µs ±   1.56 µs   215.28 µs … 219.46 µs  in 10 ×    459 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.19 µs ±   0.35 µs    52.78 µs …  53.91 µs  in 10 ×   1880 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:233 ("text - checkout (midpoint of 1000-op doc)") ok
time (mean ± σ)         range (min … max) 
 903.51 µs ±   4.93 µs   896.32 µs … 912.72 µs  in 10 ×    112 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.12 µs ±   0.06 µs     2.09 µs …   2.29 µs  in 10 ×  47858 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.41 µs ±   0.01 µs     2.39 µs …   2.43 µs  in 10 ×  41011 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:281 ("text - undo undo() (10-op group)") ok
time (mean ± σ)         range (min … max) 
  32.81 µs ±   0.10 µs    32.67 µs …  33.00 µs  in 10 ×   3027 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:295 ("text - undo undo() (50-op group)") ok
time (mean ± σ)         range (min … max) 
 593.39 µs ±   2.01 µs   589.70 µs … 596.13 µs  in 10 ×    169 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.72 µs ±   1.34 µs    39.16 µs …  43.54 µs  in 10 ×   2547 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) 
 359.58 µs ±   2.22 µs   357.29 µs … 363.74 µs  in 10 ×    280 runs
Total tests: 86, passed: 86, failed: 0.

Benchmarks run with --release flag

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: 5

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

8-15: Consider indexing parent relationships for efficiency.

The current parent lookup iterates through all registry entries and their children (O(n×m) complexity). For large ASTs, this could become a bottleneck. Consider maintaining a child_to_parent map in EditContext if profiling shows this as a hot path.

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

In `@projection/text_edit_delete.mbt` around lines 8 - 15, The parent lookup loop
(parent_info / registry / pnode.children) is O(n×m) and should be optimized by
adding and using a child->parent index; update EditContext to maintain a
child_to_parent map keyed by NodeId (or the same NodeId wrapper used here) that
stores (parent_id, ProjNode, child_index), populate/maintain it on node
insert/delete/modify operations, and replace the nested iteration in the block
that sets parent_info with a direct map lookup (using NodeId(child.node_id) as
the key) so you avoid scanning registry and children.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@projection/text_edit_binding.mbt`:
- Around line 90-99: The mirrored dependency check currently prevents valid
swaps: in the "move up" block only a dependency where the current definition
(cur_def.0) uses the previous definition (prev_def.0) is unsafe, so remove the
check that returns Err when prev_fv.contains(cur_def.0); conversely, in the
"move down" block only prev depending on cur is unsafe, so remove the mirrored
check there. Update the guards around cur_fv.contains(prev_def.0) /
prev_fv.contains(cur_def.0) (and the duplicate logic at the other block around
lines referenced by prev_fv/cur_fv) so each move-direction only tests the
dependency that actually blocks that direction (use cur_fv.contains(prev_def.0)
for move-up and prev_fv.contains(cur_def.0) for move-down).
- Around line 205-211: compute_add_binding currently delegates to
compute_insert_child using module_node_id without verifying the target node is a
Module, which can allow a stale module_node_id to mutate the wrong AST; add a
guard in compute_add_binding to resolve and validate that module_node_id
references a Module node (or return/fail early) before calling
compute_insert_child, and ensure the non-Module branch in
projection/text_edit_wrap.mbt (the rewrite path around lines 172-191) does not
silently rewrite arbitrary parents—explicitly handle or error on non-Module
parents to prevent accidental AST shape mutations.

In `@projection/text_edit_refactor.mbt`:
- Around line 186-202: The MoveCursor position is using pre-edit coordinates
(range.start) when the binding deletion (del_len at del_start) occurs before
that position; update the returned cursor to post-edit coordinates by
subtracting the deleted length (use range.start - del_len) when building the
MoveCursor in the branch that handles range.start >= binding_end (the tuple with
two edits and MoveCursor), so the cursor reflects the document after applying
the binding deletion; ensure you reference the same variables (range.start,
del_len, MoveCursor) when making the adjustment.

In `@projection/text_edit_structural.mbt`:
- Around line 8-29: In compute_unwrap: validate that keep_child_index is within
bounds (0 <= keep_child_index < node.children.length()) and return Err when out
of range; then compute the post-edit cursor by using the kept child's offset
relative to its original range: get kept_range_start (from
source_map.get_range(kept.id()) or default to range.start), compute
kept_relative = kept_start - kept_range_start, and set MoveCursor(position =
range.start + kept_relative) instead of reusing the pre-edit absolute
kept_start; refer to symbols keep_child_index, node.children, kept_start,
kept_range_start, range.start and MoveCursor.

In `@projection/text_edit_wrap.mbt`:
- Around line 122-156: In compute_insert_child, reject negative index values
before any child indexing or forwarding: add a guard that returns an Err when
index < 0 (with a clear message like "Negative index") so you never access
parent_node.children[index] or pass a negative index into insert_child_at;
ensure this check runs early (before matching on parent_node.kind) and covers
both the Module branch (which indexes parent_node.children) and the non-module
branch that calls insert_child_at.

---

Nitpick comments:
In `@projection/text_edit_delete.mbt`:
- Around line 8-15: The parent lookup loop (parent_info / registry /
pnode.children) is O(n×m) and should be optimized by adding and using a
child->parent index; update EditContext to maintain a child_to_parent map keyed
by NodeId (or the same NodeId wrapper used here) that stores (parent_id,
ProjNode, child_index), populate/maintain it on node insert/delete/modify
operations, and replace the nested iteration in the block that sets parent_info
with a direct map lookup (using NodeId(child.node_id) as the key) so you avoid
scanning registry and children.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: da01069b-228d-45a3-b568-2d49923e216c

📥 Commits

Reviewing files that changed from the base of the PR and between 7aa1278 and 56bc095.

📒 Files selected for processing (14)
  • editor/tree_edit_bridge.mbt
  • projection/pkg.generated.mbti
  • projection/text_edit.mbt
  • projection/text_edit_binding.mbt
  • projection/text_edit_commit.mbt
  • projection/text_edit_delete.mbt
  • projection/text_edit_drop.mbt
  • projection/text_edit_middleware.mbt
  • projection/text_edit_refactor.mbt
  • projection/text_edit_rename.mbt
  • projection/text_edit_structural.mbt
  • projection/text_edit_utils.mbt
  • projection/text_edit_wbtest.mbt
  • projection/text_edit_wrap.mbt

…dations

- Move binding scoping: remove over-restrictive guards that blocked valid
  swaps (move-up rejected when prev used cur, move-down rejected when cur
  used next — both safe after swap)
- InlineDefinition cursor: use post-edit coordinates (range.start - del_len)
  when binding deletion precedes the var
- Unwrap cursor: use range.start (replacement start) instead of kept child's
  pre-edit position which is invalidated by the edit
- AddBinding: validate target is a Module node before delegating
- InsertChild: reject negative index values
- Unwrap: add lower-bound check on keep_child_index

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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: 1

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

274-279: Consider handling missing source ranges explicitly.

If source_map.get_range(uid) returns None for a usage, it's silently skipped. This could lead to an inconsistent state where the binding is deleted (line 293) but some usages remain unreplaced, resulting in dangling references.

While this edge case is unlikely for normal source code, defensive handling would improve robustness.

♻️ Optional: Fail if any usage lacks a source range
   for uid in usage_ids {
     match source_map.get_range(uid) {
       Some(r) => usage_ranges.push((r.start, r.end))
-      None => ()
+      None => return Err("Usage node missing source range")
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projection/text_edit_refactor.mbt` around lines 274 - 279, The loop over
usage_ids silently skips uids where source_map.get_range(uid) returns None,
which can leave usages unreplaced after binding deletion (usage_ranges is built
from ranges and the binding is removed later); update the logic that iterates
usage_ids to explicitly handle missing ranges by collecting missing uids and
returning an error or aborting (or logging and skipping the entire refactor) if
any are missing instead of silently ignoring them—modify the block that builds
usage_ranges (the for uid in usage_ids { match source_map.get_range(uid) { ... }
} loop) to detect None cases and fail-fast or surface a clear error referencing
the offending uid(s) before proceeding to the code that deletes the binding.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@projection/text_edit_wbtest.mbt`:
- Around line 1185-1195: The test's module_id is actually taken from
fp.final_expr.unwrap().id() (the final expression Var("x")), so the failure may
be due to an un-insertable parent rather than the negative index; update the
test so InsertChild targets a real insertable module node: obtain the actual
module ID from the parsed file (instead of fp.final_expr.unwrap().id()) and pass
that ID to InsertChild, then assert Err(_) to verify negative index handling;
alternatively, if you intend to keep the existing parent, rename module_id to
something like final_expr_id to reflect its true value so the test name matches
behavior. Ensure references to compute_text_edit, InsertChild, make_edit_ctx and
fp are updated accordingly.

---

Nitpick comments:
In `@projection/text_edit_refactor.mbt`:
- Around line 274-279: The loop over usage_ids silently skips uids where
source_map.get_range(uid) returns None, which can leave usages unreplaced after
binding deletion (usage_ranges is built from ranges and the binding is removed
later); update the logic that iterates usage_ids to explicitly handle missing
ranges by collecting missing uids and returning an error or aborting (or logging
and skipping the entire refactor) if any are missing instead of silently
ignoring them—modify the block that builds usage_ranges (the for uid in
usage_ids { match source_map.get_range(uid) { ... } } loop) to detect None cases
and fail-fast or surface a clear error referencing the offending uid(s) before
proceeding to the code that deletes the binding.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e03668a6-8267-4ce9-9dd2-bca2076aaf26

📥 Commits

Reviewing files that changed from the base of the PR and between 56bc095 and d80cf94.

📒 Files selected for processing (5)
  • projection/text_edit_binding.mbt
  • projection/text_edit_refactor.mbt
  • projection/text_edit_structural.mbt
  • projection/text_edit_wbtest.mbt
  • projection/text_edit_wrap.mbt
🚧 Files skipped from review as they are similar to previous changes (3)
  • projection/text_edit_structural.mbt
  • projection/text_edit_wrap.mbt
  • projection/text_edit_binding.mbt

Comment on lines +1185 to +1195
///|
test "compute_text_edit: InsertChild rejects negative index" {
let text = "let x = 0\nx"
let (_, source_map, registry, fp) = parse_with_token_spans(text)
let module_id = fp.final_expr.unwrap().id()
let result = compute_text_edit(
InsertChild(parent=module_id, index=-1, kind=@ast.Term::Int(0)),
make_edit_ctx(text, source_map, registry, fp),
)
inspect(result is Err(_), content="true")
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Misleading variable name and potential test coverage gap.

The variable module_id is assigned from fp.final_expr.unwrap().id(), which retrieves the final expression's ID (Var("x")), not the module's ID. This means the test may be rejected due to the parent not being insertable, rather than the negative index validation the test name claims to verify.

Consider either:

  1. Use the actual module ID to isolate the negative index validation
  2. Rename the variable to reflect what it actually holds
Proposed fix to test negative index on a valid parent
 test "compute_text_edit: InsertChild rejects negative index" {
   let text = "let x = 0\nx"
-  let (_, source_map, registry, fp) = parse_with_token_spans(text)
-  let module_id = fp.final_expr.unwrap().id()
+  let (proj, source_map, registry, fp) = parse_with_token_spans(text)
+  let module_id = proj.id()
   let result = compute_text_edit(
     InsertChild(parent=module_id, index=-1, kind=@ast.Term::Int(0)),
     make_edit_ctx(text, source_map, registry, fp),
   )
   inspect(result is Err(_), content="true")
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projection/text_edit_wbtest.mbt` around lines 1185 - 1195, The test's
module_id is actually taken from fp.final_expr.unwrap().id() (the final
expression Var("x")), so the failure may be due to an un-insertable parent
rather than the negative index; update the test so InsertChild targets a real
insertable module node: obtain the actual module ID from the parsed file
(instead of fp.final_expr.unwrap().id()) and pass that ID to InsertChild, then
assert Err(_) to verify negative index handling; alternatively, if you intend to
keep the existing parent, rename module_id to something like final_expr_id to
reflect its true value so the test name matches behavior. Ensure references to
compute_text_edit, InsertChild, make_edit_ctx and fp are updated accordingly.

- Add doc comments to EditContext, core_dispatch, EditMiddleware trait,
  ValidateNodeExists, extract_primary_node_id, find_def_index,
  binding_delete_range
- Update design doc: remove stale wrap() closure, add Phase 5 (bug fixes),
  update file layout and testing sections

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@dowdiness dowdiness merged commit e09f308 into main Mar 23, 2026
15 of 16 checks passed
dowdiness added a commit that referenced this pull request Mar 23, 2026
- Mark text_edit decomposition as done (PR #54)
- Add follow-up items: Term enum extensibility, AST transform pipeline,
  coordinate arithmetic audit, parent lookup index

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
dowdiness added a commit that referenced this pull request Mar 23, 2026
Audited all 9 MoveCursor sites across handler files.
No new bugs found — the two cursor position bugs (unwrap, inline_definition)
were already fixed in the handler chain PR (#54).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
dowdiness added a commit that referenced this pull request Mar 23, 2026
* docs: add handler chain follow-up TODOs

- Mark text_edit decomposition as done (PR #54)
- Add follow-up items: Term enum extensibility, AST transform pipeline,
  coordinate arithmetic audit, parent lookup index

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: mark coordinate arithmetic audit as done

Audited all 9 MoveCursor sites across handler files.
No new bugs found — the two cursor position bugs (unwrap, inline_definition)
were already fixed in the handler chain PR (#54).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
dowdiness added a commit that referenced this pull request Mar 24, 2026
- Test count: 472 → 507
- Add handler chain files (PR #54) to file map and Task 5 Step 3
- Add recovery/recovery_epoch fields to SyncEditor struct
- EditContext[T] parameterization for middleware pipeline

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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