Skip to content

feat: Framework Extraction Phase 1 — generic ProjNode[T] + SyncEditor[T]#57

Merged
dowdiness merged 14 commits intomainfrom
feature/framework-extraction
Mar 24, 2026
Merged

feat: Framework Extraction Phase 1 — generic ProjNode[T] + SyncEditor[T]#57
dowdiness merged 14 commits intomainfrom
feature/framework-extraction

Conversation

@dowdiness
Copy link
Owner

@dowdiness dowdiness commented Mar 24, 2026

Summary

  • Parameterize ProjNode[T], SyncEditor[T], TreeEditorState[T], EditContext[T] over a generic AST type T constrained by TreeNode + Renderable traits
  • Lambda calculus (@ast.Term) implements both traits — existing behavior unchanged
  • SyncEditor::new_lambda preserves backward compatibility for all consumers

Architecture

Two capability traits in projection/traits.mbt:

  • TreeNodechildren(Self) -> Array[Self], same_kind(Self, Self) -> Bool
  • Renderablekind_tag, label, placeholder, unparse

Any MoonBit type implementing these traits can now be used with the projection pipeline. The framework supports any grammar that provides a parser + AST structure.

Changes

56 files changed (+917, -819)

  • projection/traits.mbt — new TreeNode and Renderable trait definitions
  • projection/traits_term.mbtimpl TreeNode + Renderable for @ast.Term
  • projection/proj_node.mbtProjNodeProjNode[T]
  • projection/reconcile_ast.mbt — generic reconcile[T], backward-compat reconcile_ast alias
  • projection/tree_editor_model.mbtInteractiveTreeNode[T], removed InteractiveNodeShape
  • projection/text_edit.mbtEditContext[T], handler files parameterized
  • editor/sync_editor.mbtSyncEditor[T] with make_parser factory
  • crdt.mbt — concrete SyncEditor[@ast.Term], uses new_lambda
  • All test files updated

What this enables

// Any grammar can now use the projection pipeline:
let editor = SyncEditor::new(
  agent_id,
  make_parser=fn(s) { my_parser(s, my_grammar) },
  build_memos=my_memo_builder,
)

Test plan

  • 507/507 tests pass (0 new, 0 removed — pure refactoring)
  • moon check passes (3 cosmetic warnings from MoonBit trait bound detection)
  • moon info && moon fmt clean

Follow-up (Phase 2)

  1. Generic pipeline path — bypass FlatProj for non-lambda grammars
  2. Package extraction — move traits + core to framework/, lambda code to lang/lambda/
  3. TestExpr test suite — framework tests using minimal test expression type

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor
    • Editor and projection systems generalized to be term-parameterized, enabling lambda/term-aware projections and editor behavior (improves correctness for lambda-specific editing and projections).
  • Tests
    • Test suite updated to exercise the new lambda-based editor construction; test behavior and assertions unchanged.
  • Chores
    • Public API signatures adjusted: sync-apply now returns a textual result and the exported editor accessor is tightened to the lambda-typed editor.

dowdiness and others added 4 commits March 24, 2026 20:15
Add two capability traits that will parameterize ProjNode[T]:
- TreeNode: structural access (children, same_kind) for reconciliation
- Renderable: display/serialization (kind_tag, label, placeholder, unparse)

Both traits are implemented for @ast.Term. Pure additions — no existing
code changes.

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

Tasks 2-5 of framework extraction: parameterize the entire projection/
package over a generic AST type T constrained by TreeNode + Renderable.

- ProjNode[T]: generic struct replacing hardcoded @ast.Term
- reconcile[T]: generic LCS-based reconciliation (reconcile_ast kept as alias)
- InteractiveTreeNode[T], TreeEditorState[T]: generic editor model
- InteractiveNodeStamp stores kind: T directly (replaces InteractiveNodeShape)
- EditContext[T]: generic edit context (handlers stay Term-specific)
- SourceMap methods: generic over ProjNode[T] (struct stays non-generic)
- same_kind_tag removed, replaced by TreeNode::same_kind trait method

Behavioral changes:
- Stamp comparison uses T : Eq instead of shape enum (detects leaf value changes)
- Bop operator changes now get fresh node IDs (more correct)

All 186 projection tests pass. Editor package has expected compilation
errors (19x @proj.ProjNode expects 1 argument) to be resolved in Task 6.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SyncEditor is now generic over the AST type T. The constructor accepts
a parser factory and memo builder, decoupling it from the lambda calculus
grammar. Lambda-specific methods (get_ast, get_resolution, apply_tree_edit)
stay as SyncEditor[@ast.Term] specializations.

- SyncEditor::new[T] takes make_parser + build_memos factories
- SyncEditor::new_lambda convenience constructor for @ast.Term
- All generic methods use fn[T] syntax (66 method signatures updated)
- get_proj_node and get_node require T : Eq for Memo access
- projection_memo pipeline stays lambda-specific internally
- All 507 tests pass, no new warnings

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

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Mar 24, 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 71d3357 Commit Preview URL

Branch Preview URL
Mar 24 2026, 03:05 PM

@coderabbitai
Copy link

coderabbitai bot commented Mar 24, 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 9 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: 2604fe24-7494-4e52-b504-2ba0185311b7

📥 Commits

Reviewing files that changed from the base of the PR and between 2b4e6e6 and 71d3357.

📒 Files selected for processing (12)
  • examples/ideal/main/main.mbt
  • examples/ideal/main/model.mbt
  • examples/ideal/main/view_inspector.mbt
  • examples/ideal/main/view_outline.mbt
  • examples/rabbita/main/benchmark_support.mbt
  • examples/rabbita/main/main.mbt
  • examples/rabbita/main/moon.pkg
  • examples/rabbita/moon.mod.json
  • projection/pkg.generated.mbti
  • projection/tree_editor_model.mbt
  • projection/tree_editor_refresh.mbt
  • projection/tree_editor_wbtest.mbt
📝 Walkthrough

Walkthrough

The PR generically parameterizes the editor and projection stack around a type parameter T (specializing many APIs to @ast.Term), adds TreeNode/Renderable traits with term implementations, and introduces a SyncEditor::new_lambda constructor while updating call sites and exports to use the typed editor.

Changes

Cohort / File(s) Summary
SyncEditor core & API
editor/sync_editor.mbt, editor/sync_editor_*.mbt, editor/pkg.generated.mbti
Converted SyncEditorSyncEditor[T]; added new_lambda(...) -> SyncEditor[@ast.Term]; updated constructors, stored parser/memo types, and made all public methods generic over T (ws, sync, text, undo, parser helpers).
CRDT registry & bindings
crdt.mbt, crdt_test.mbt, crdt_undo.mbt, pkg.generated.mbti
Editor registry and public accessors now use @editor.SyncEditor[@ast.Term]; editor creation sites changed to SyncEditor::new_lambda(...); apply_sync_json return type adjusted in package bindings.
Projection genericification
projection/proj_node.mbt, projection/*.mbt (many: flat_proj.mbt, reconcile_ast.mbt, proj_node_json.mbt, scope.mbt, source_map.mbt, text_edit*.mbt, traits*.mbt, tree_*/*.mbt, tree_lens.mbt, ...)
Parameterized projection types: ProjNode[T], EditContext[T], TreeEditorState[T], InteractiveTreeNode[T], etc.; introduced TreeNode and Renderable traits and implementations for @ast.Term; replaced pattern-matching reconciliation with generic reconcile[T] using T::same_kind.
Projection memo & projection-builder
editor/projection_memo.mbt
Replaced build_projection_memos with build_lambda_projection_memos and updated memo/registry types to term-parameterized forms (ProjNode[@ast.Term]), adding a lambda parser parameter.
Text-edit & EditContext surface
projection/text_edit.mbt, projection/text_edit_*.mbt, projection/text_edit_*_wbtest.mbt
Made EditContext generic (EditContext[T]) and specialized edit computation APIs to EditContext[@ast.Term]; updated many compute_* signatures and middleware types accordingly.
Tests & whitebox tests
editor/*_test.mbt, editor/*_wbtest.mbt, projection/*_wbtest.mbt, integration_ws_test.mbt, tree_editor_*_wbtest.mbt
Updated test helpers and instantiations to use SyncEditor::new_lambda(...) and ProjNode[@ast.Term]/typed helpers; test assertions and logic unchanged.
Examples & benchmarks
examples/..., editor/performance_benchmark.mbt, examples/rabbita/...
Updated model/editor fields to use @editor.SyncEditor[@ast.Term] and replaced SyncEditor::new(...) calls with new_lambda(...) in benchmarks and app init code.
Package manifest
moon.pkg
Added dowdiness/lambda/ast import (@ast) to enable @ast.Term references in package exports.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I hopped through types and left a trail,
Making editors generic, light and hale.
Lambdas whispered, traits took flight,
ProjNodes donned a new, typed sight,
The code now leaps — a rabbit's tale! 🥕

🚥 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 and specifically describes the main change: introducing generic parameterization of ProjNode[T] and SyncEditor[T] as Phase 1 of framework extraction.
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/framework-extraction

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.

@github-actions
Copy link

github-actions bot commented Mar 24, 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) 
 625.07 µs ± 116.32 µs   467.82 µs … 792.62 µs  in 10 ×    238 runs
[dowdiness/canopy] bench editor/performance_benchmark.mbt:34 ("projection pipeline - incremental keystroke (80 defs)") ok
time (mean ± σ)         range (min … max) 
 943.88 µs ±  69.09 µs   842.08 µs …   1.05 ms  in 10 ×    122 runs
[dowdiness/canopy] bench editor/performance_benchmark.mbt:39 ("projection pipeline - incremental keystroke (320 defs)") ok
time (mean ± σ)         range (min … max) 
   3.36 ms ±  27.95 µs     3.31 ms …   3.42 ms  in 10 ×     31 runs
[dowdiness/canopy] bench editor/performance_benchmark.mbt:44 ("projection pipeline - incremental keystroke (500 defs)") ok
time (mean ± σ)         range (min … max) 
   5.99 ms ± 218.53 µs     5.76 ms …   6.35 ms  in 10 ×     18 runs
[dowdiness/canopy] bench editor/performance_benchmark.mbt:49 ("projection pipeline - incremental keystroke (1000 defs)") ok
time (mean ± σ)         range (min … max) 
  14.48 ms ± 582.13 µs    13.77 ms …  15.86 ms  in 10 ×      8 runs
[dowdiness/canopy] bench editor/performance_benchmark.mbt:87 ("parser benchmark - reactive full reparse medium") ok
time (mean ± σ)         range (min … max) 
 340.95 µs ±   7.47 µs   332.70 µs … 358.50 µs  in 10 ×    237 runs
[dowdiness/canopy] bench editor/performance_benchmark.mbt:108 ("parser benchmark - imperative incremental medium") ok
time (mean ± σ)         range (min … max) 
 124.13 µs ±   0.47 µs   123.55 µs … 125.24 µs  in 10 ×    792 runs
[dowdiness/canopy] bench editor/performance_benchmark.mbt:133 ("parser benchmark - reactive full reparse large") ok
time (mean ± σ)         range (min … max) 
   1.31 ms ±   9.30 µs     1.30 ms …   1.33 ms  in 10 ×     75 runs
[dowdiness/canopy] bench editor/performance_benchmark.mbt:154 ("parser benchmark - imperative incremental large") ok
time (mean ± σ)         range (min … max) 
 491.82 µs ±   3.01 µs   488.23 µs … 497.95 µs  in 10 ×    203 runs
[dowdiness/canopy] bench projection/tree_refresh_benchmark.mbt:32 ("tree refresh unchanged (20 defs)") ok
time (mean ± σ)         range (min … max) 
   5.38 µs ±   0.14 µs     5.28 µs …   5.62 µs  in 10 ×  18130 runs
[dowdiness/canopy] bench projection/tree_refresh_benchmark.mbt:42 ("tree refresh unchanged (80 defs)") ok
time (mean ± σ)         range (min … max) 
  22.19 µs ±   0.26 µs    22.01 µs …  22.86 µs  in 10 ×   4420 runs
[dowdiness/canopy] bench projection/tree_refresh_benchmark.mbt:52 ("tree refresh unchanged (320 defs)") ok
time (mean ± σ)         range (min … max) 
  89.56 µs ±   1.43 µs    88.87 µs …  93.61 µs  in 10 ×   1121 runs
[dowdiness/canopy] bench projection/tree_refresh_benchmark.mbt:62 ("tree refresh unchanged (1000 defs)") ok
time (mean ± σ)         range (min … max) 
 313.50 µs ±   0.88 µs   312.17 µs … 314.50 µs  in 10 ×    313 runs
[dowdiness/canopy] bench projection/tree_refresh_benchmark.mbt:74 ("tree refresh 1 changed (20 defs)") ok
time (mean ± σ)         range (min … max) 
   9.88 µs ±   0.01 µs     9.86 µs …   9.90 µs  in 10 ×  10128 runs
[dowdiness/canopy] bench projection/tree_refresh_benchmark.mbt:89 ("tree refresh 1 changed (320 defs)") ok
time (mean ± σ)         range (min … max) 
 173.41 µs ±   0.51 µs   172.72 µs … 174.16 µs  in 10 ×    577 runs
[dowdiness/canopy] bench projection/tree_refresh_benchmark.mbt:104 ("tree refresh 1 changed (1000 defs)") ok
time (mean ± σ)         range (min … max) 
 538.79 µs ±   2.96 µs   534.31 µs … 542.73 µs  in 10 ×    184 runs
[dowdiness/canopy] bench projection/tree_refresh_benchmark.mbt:119 ("tree refresh 1 changed (80 defs)") ok
time (mean ± σ)         range (min … max) 
  39.56 µs ±   0.07 µs    39.45 µs …  39.67 µs  in 10 ×   2536 runs
Total tests: 17, passed: 17, failed: 0.

PR branch:

Registry index cloned successfully
Symbols updated successfully
Downloading moonbitlang/x@0.4.38
Downloading moonbitlang/quickcheck@0.9.10
[dowdiness/canopy] bench editor/performance_benchmark.mbt:29 ("projection pipeline - incremental keystroke (20 defs)") ok
time (mean ± σ)         range (min … max) 
 625.85 µs ± 125.27 µs   472.85 µs … 815.16 µs  in 10 ×    244 runs
[dowdiness/canopy] bench editor/performance_benchmark.mbt:34 ("projection pipeline - incremental keystroke (80 defs)") ok
time (mean ± σ)         range (min … max) 
 978.56 µs ±  79.17 µs   874.79 µs …   1.10 ms  in 10 ×    124 runs
[dowdiness/canopy] bench editor/performance_benchmark.mbt:39 ("projection pipeline - incremental keystroke (320 defs)") ok
time (mean ± σ)         range (min … max) 
   3.39 ms ±  55.70 µs     3.31 ms …   3.48 ms  in 10 ×     30 runs
[dowdiness/canopy] bench editor/performance_benchmark.mbt:44 ("projection pipeline - incremental keystroke (500 defs)") ok
time (mean ± σ)         range (min … max) 
   6.17 ms ± 170.87 µs     5.96 ms …   6.42 ms  in 10 ×     17 runs
[dowdiness/canopy] bench editor/performance_benchmark.mbt:49 ("projection pipeline - incremental keystroke (1000 defs)") ok
time (mean ± σ)         range (min … max) 
  16.40 ms ±   1.81 ms    14.84 ms …  21.02 ms  in 10 ×      6 runs
[dowdiness/canopy] bench editor/performance_benchmark.mbt:87 ("parser benchmark - reactive full reparse medium") ok
time (mean ± σ)         range (min … max) 
 368.74 µs ±  35.28 µs   327.98 µs … 434.38 µs  in 10 ×    235 runs
[dowdiness/canopy] bench editor/performance_benchmark.mbt:108 ("parser benchmark - imperative incremental medium") ok
time (mean ± σ)         range (min … max) 
 126.39 µs ±   1.40 µs   123.68 µs … 128.64 µs  in 10 ×    757 runs
[dowdiness/canopy] bench editor/performance_benchmark.mbt:133 ("parser benchmark - reactive full reparse large") ok
time (mean ± σ)         range (min … max) 
   1.32 ms ±  12.51 µs     1.30 ms …   1.34 ms  in 10 ×     76 runs
[dowdiness/canopy] bench editor/performance_benchmark.mbt:154 ("parser benchmark - imperative incremental large") ok
time (mean ± σ)         range (min … max) 
 485.99 µs ±   1.63 µs   484.14 µs … 488.78 µs  in 10 ×    206 runs
[dowdiness/canopy] bench projection/tree_refresh_benchmark.mbt:38 ("tree refresh unchanged (20 defs)") ok
time (mean ± σ)         range (min … max) 
   5.01 µs ±   0.09 µs     4.94 µs …   5.20 µs  in 10 ×  19776 runs
[dowdiness/canopy] bench projection/tree_refresh_benchmark.mbt:48 ("tree refresh unchanged (80 defs)") ok
time (mean ± σ)         range (min … max) 
  18.22 µs ±   0.22 µs    18.04 µs …  18.70 µs  in 10 ×   5433 runs
[dowdiness/canopy] bench projection/tree_refresh_benchmark.mbt:58 ("tree refresh unchanged (320 defs)") ok
time (mean ± σ)         range (min … max) 
  80.54 µs ±   1.37 µs    79.89 µs …  84.38 µs  in 10 ×   1249 runs
[dowdiness/canopy] bench projection/tree_refresh_benchmark.mbt:68 ("tree refresh unchanged (1000 defs)") ok
time (mean ± σ)         range (min … max) 
 285.75 µs ±   1.26 µs   284.14 µs … 288.07 µs  in 10 ×    345 runs
[dowdiness/canopy] bench projection/tree_refresh_benchmark.mbt:80 ("tree refresh 1 changed (20 defs)") ok
time (mean ± σ)         range (min … max) 
   8.44 µs ±   0.02 µs     8.41 µs …   8.47 µs  in 10 ×  11891 runs
[dowdiness/canopy] bench projection/tree_refresh_benchmark.mbt:95 ("tree refresh 1 changed (320 defs)") ok
time (mean ± σ)         range (min … max) 
 142.98 µs ±   0.36 µs   142.54 µs … 143.60 µs  in 10 ×    691 runs
[dowdiness/canopy] bench projection/tree_refresh_benchmark.mbt:110 ("tree refresh 1 changed (1000 defs)") ok
time (mean ± σ)         range (min … max) 
 501.76 µs ±   1.91 µs   499.67 µs … 506.17 µs  in 10 ×    200 runs
[dowdiness/canopy] bench projection/tree_refresh_benchmark.mbt:125 ("tree refresh 1 changed (80 defs)") ok
time (mean ± σ)         range (min … max) 
  30.81 µs ±   0.05 µs    30.75 µs …  30.90 µs  in 10 ×   3256 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.09 µs ±   0.75 µs    31.57 µs …  34.03 µs  in 10 ×   3020 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) 
 195.92 µs ±   0.92 µs   194.64 µs … 197.48 µs  in 10 ×    511 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) 
 971.46 µs ±   3.80 µs   966.29 µs … 979.13 µs  in 10 ×    103 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) 
 289.00 µs ±   1.44 µs   287.55 µs … 292.57 µs  in 10 ×    347 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) 
 166.91 µs ±   0.86 µs   165.97 µs … 168.55 µs  in 10 ×    596 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) 
 108.88 µs ±   0.27 µs   108.49 µs … 109.22 µs  in 10 ×    913 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) 
 257.65 µs ±   0.95 µs   256.58 µs … 259.60 µs  in 10 ×    388 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_merge_benchmark.mbt:341 ("merge - retreat concurrent deletes (500 items, 50 deletes)") ok
time (mean ± σ)         range (min … max) 
 450.60 µs ±   0.90 µs   449.45 µs … 451.54 µs  in 10 ×    222 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_merge_benchmark.mbt:408 ("merge - context apply operations (50 ops)") ok
time (mean ± σ)         range (min … max) 
  10.70 µs ±   0.49 µs    10.45 µs …  11.89 µs  in 10 ×   9156 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:6 ("branch - checkout (10 ops)") ok
time (mean ± σ)         range (min … max) 
   7.96 µs ±   0.04 µs     7.91 µs …   8.01 µs  in 10 ×  12609 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:29 ("branch - checkout (100 ops)") ok
time (mean ± σ)         range (min … max) 
 109.40 µs ±   0.34 µs   108.81 µs … 110.08 µs  in 10 ×    923 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:47 ("branch - checkout (1000 ops)") ok
time (mean ± σ)         range (min … max) 
   1.86 ms ±   8.41 µs     1.85 ms …   1.88 ms  in 10 ×     54 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:65 ("branch - advance (10 new ops)") ok
time (mean ± σ)         range (min … max) 
  36.41 µs ±   0.22 µs    36.16 µs …  36.76 µs  in 10 ×   2761 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:91 ("branch - advance (100 new ops)") ok
time (mean ± σ)         range (min … max) 
 154.19 µs ±   1.91 µs   152.70 µs … 158.92 µs  in 10 ×    654 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:117 ("branch - checkout with concurrent branches") ok
time (mean ± σ)         range (min … max) 
 111.45 µs ±   0.47 µs   110.58 µs … 112.02 µs  in 10 ×    899 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:145 ("branch - checkout with deletes") ok
time (mean ± σ)         range (min … max) 
 168.13 µs ±   0.61 µs   167.17 µs … 169.00 µs  in 10 ×    596 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.60 µs ±   0.54 µs   135.02 µs … 136.48 µs  in 10 ×    744 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) 
  17.48 ms ±   4.16 ms    11.69 ms …  23.40 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) 
  11.04 µs ±   0.06 µs    10.96 µs …  11.11 µs  in 10 ×   9030 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:264 ("branch - to_text (1000 chars)") ok
time (mean ± σ)         range (min … max) 
 114.97 µs ±   0.60 µs   114.29 µs … 116.00 µs  in 10 ×    872 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.81 µs ±   0.09 µs    26.68 µs …  26.93 µs  in 10 ×   3750 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:311 ("branch - single advance (50 new ops)") ok
time (mean ± σ)         range (min … max) 
  85.49 µs ±   0.66 µs    84.67 µs …  86.86 µs  in 10 ×   1166 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:342 ("branch - realistic typing (50 chars)") ok
time (mean ± σ)         range (min … max) 
  79.34 ms ±  21.61 ms    48.79 ms … 108.43 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) 
  24.73 µs ±   0.25 µs    24.54 µs …  25.30 µs  in 10 ×   4063 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.70 µs ±   0.04 µs     4.62 µs …   4.75 µs  in 10 ×  21138 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.60 µs ±   1.58 µs    78.63 µs …  83.54 µs  in 10 ×   1216 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.39 ms ±  10.28 µs     1.38 ms …   1.41 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) 
  79.04 µs ±   0.16 µs    78.86 µs …  79.35 µs  in 10 ×   1266 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.47 µs ±   0.20 µs    77.22 µs …  77.81 µs  in 10 ×   1288 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.54 µs ±   0.50 µs   149.81 µs … 151.30 µs  in 10 ×    667 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.09 µs    32.46 µs …  32.76 µs  in 10 ×   3072 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.90 µs ±   0.32 µs    62.67 µs …  63.76 µs  in 10 ×   1586 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.38 ms ± 702.64 µs    21.56 ms …  23.50 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) 
 637.19 ms ±  54.43 ms   567.29 ms … 705.31 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) 
 700.09 ms ±  72.36 ms   597.17 ms … 791.22 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.54 µs ±   0.00 µs     1.53 µs …   1.54 µs  in 10 ×  65077 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.61 µ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.44 µs ±   0.01 µs     2.43 µs …   2.45 µs  in 10 ×  41058 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.31 µs …   1.31 µs  in 10 ×  76389 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.52 µs ±   0.04 µs    19.48 µs …  19.58 µs  in 10 ×   5117 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.17 µs ±   0.06 µs    19.08 µs …  19.27 µs  in 10 ×   5201 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/fugue/jump_ancestors_benchmark.mbt:2 ("jump - sequential append 1K") ok
time (mean ± σ)         range (min … max) 
 152.97 µs ±   0.68 µs   152.17 µs … 154.02 µs  in 10 ×    649 runs
[dowdiness/event-graph-walker] bench internal/fugue/jump_ancestors_benchmark.mbt:26 ("jump - concurrent insert into 1K doc") ok
time (mean ± σ)         range (min … max) 
 150.90 µs ±   0.63 µs   150.14 µs … 151.75 µs  in 10 ×    663 runs
[dowdiness/event-graph-walker] bench internal/fugue/jump_ancestors_benchmark.mbt:58 ("jump - concurrent insert into 10K doc") ok
time (mean ± σ)         range (min … max) 
   4.36 ms ±  84.45 µs     4.25 ms …   4.46 ms  in 10 ×     24 runs
[dowdiness/event-graph-walker] bench internal/fugue/jump_ancestors_benchmark.mbt:90 ("jump - degenerate chain remote insert") ok
time (mean ± σ)         range (min … max) 
 151.35 µs ±   0.32 µs   150.85 µs … 151.85 µs  in 10 ×    663 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:6 ("oplog - insert (100 ops)") ok
time (mean ± σ)         range (min … max) 
  64.03 µs ±   1.51 µs    62.82 µs …  67.65 µs  in 10 ×   1537 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:20 ("oplog - insert (1000 ops)") ok
time (mean ± σ)         range (min … max) 
 809.29 µs ±   2.12 µs   805.77 µs … 811.64 µ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) 
  96.33 µs ±   0.79 µs    95.27 µs …  98.22 µs  in 10 ×   1042 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:58 ("oplog - apply_remote (50 ops)") ok
time (mean ± σ)         range (min … max) 
  38.28 µs ±   0.05 µs    38.23 µs …  38.37 µs  in 10 ×   2602 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.10 µs ±   0.60 µs    88.26 µs …  89.98 µs  in 10 ×   1117 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:150 ("oplog - walk_and_collect (concurrent)") ok
time (mean ± σ)         range (min … max) 
  92.52 µs ±   0.40 µs    91.58 µs …  92.91 µs  in 10 ×   1078 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.71 µs ±   0.28 µs    42.36 µs …  43.32 µs  in 10 ×   2352 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:202 ("oplog - walk_filtered (inserts only)") ok
time (mean ± σ)         range (min … max) 
  63.49 µs ±   0.18 µs    63.29 µs …  63.78 µ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) 
 366.14 µs ±   1.07 µs   364.38 µs … 367.64 µ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.53 µs ±   0.21 µs    63.27 µs …  63.86 µ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) 
 248.95 ms ±  43.20 ms   202.26 ms … 303.22 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) 
 755.04 ms ±  37.48 ms   683.39 ms … 832.65 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) 
  85.54 µs ±   0.22 µs    85.25 µs …  85.89 µs  in 10 ×   1097 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:22 ("text - insert append (1000 chars)") ok
time (mean ± σ)         range (min … max) 
   1.20 ms ±   4.13 µs     1.19 ms …   1.21 ms  in 10 ×     79 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:35 ("text - insert prepend (100 chars)") ok
time (mean ± σ)         range (min … max) 
 901.70 µs ±  16.47 µs   887.77 µs … 932.37 µs  in 10 ×    110 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) 
 262.71 µs ±   0.95 µs   261.60 µs … 264.02 µs  in 10 ×    379 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:70 ("text - text() (100-char doc)") ok
time (mean ± σ)         range (min … max) 
  13.28 µs ±   0.15 µs    13.07 µs …  13.50 µs  in 10 ×   7496 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:83 ("text - text() (1000-char doc)") ok
time (mean ± σ)         range (min … max) 
 140.05 µs ±   0.68 µs   138.31 µs … 140.61 µs  in 10 ×    730 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) 
 542.16 µs ±   2.79 µs   539.67 µs … 547.53 µs  in 10 ×    182 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:156 ("text - sync apply (50 remote ops)") ok
time (mean ± σ)         range (min … max) 
 101.18 µs ±   0.54 µs   100.38 µs … 101.86 µs  in 10 ×    970 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:171 ("text - sync apply (500 remote ops)") ok
time (mean ± σ)         range (min … max) 
   1.51 ms ±   9.42 µs     1.49 ms …   1.52 ms  in 10 ×     66 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) 
 211.12 µs ±   0.61 µs   210.05 µs … 211.98 µs  in 10 ×    471 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:216 ("text - checkout (midpoint of 100-op doc)") ok
time (mean ± σ)         range (min … max) 
  50.50 µs ±   0.13 µs    50.33 µs …  50.74 µs  in 10 ×   1987 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:233 ("text - checkout (midpoint of 1000-op doc)") ok
time (mean ± σ)         range (min … max) 
 822.58 µs ±   4.55 µs   817.53 µs … 832.80 µs  in 10 ×    123 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.08 µs ±   0.00 µs     2.07 µs …   2.08 µs  in 10 ×  48192 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.48 µs  in 10 ×  41337 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:281 ("text - undo undo() (10-op group)") ok
time (mean ± σ)         range (min … max) 
  30.12 µs ±   0.11 µs    29.93 µs …  30.26 µs  in 10 ×   3320 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:295 ("text - undo undo() (50-op group)") ok
time (mean ± σ)         range (min … max) 
 449.61 µs ±   3.03 µs   445.28 µs … 454.14 µs  in 10 ×    222 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) 
  35.94 µs ±   0.11 µs    35.83 µs …  36.21 µs  in 10 ×   2768 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) 
 323.93 µs ±   0.98 µs   322.98 µs … 325.98 µs  in 10 ×    310 runs
Total tests: 91, passed: 91, 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.40 µs ±   1.06 µs    31.49 µs …  34.40 µs  in 10 ×   3033 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) 
 194.22 µs ±   1.52 µs   192.86 µs … 197.06 µs  in 10 ×    511 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) 
 997.80 µs ±  39.51 µs   958.64 µs …   1.07 ms  in 10 ×    105 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) 
 288.23 µs ±   2.62 µs   284.77 µs … 291.22 µs  in 10 ×    344 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) 
 165.39 µs ±   0.90 µs   164.28 µs … 166.72 µs  in 10 ×    599 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) 
 108.31 µs ±   0.38 µs   107.88 µs … 109.03 µs  in 10 ×    921 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) 
 256.68 µs ±   0.48 µs   255.89 µs … 257.33 µs  in 10 ×    389 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_merge_benchmark.mbt:341 ("merge - retreat concurrent deletes (500 items, 50 deletes)") ok
time (mean ± σ)         range (min … max) 
 447.32 µs ±   1.55 µs   445.68 µs … 449.69 µs  in 10 ×    223 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_merge_benchmark.mbt:408 ("merge - context apply operations (50 ops)") ok
time (mean ± σ)         range (min … max) 
  10.64 µs ±   0.49 µs    10.39 µs …  11.82 µs  in 10 ×   9354 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:6 ("branch - checkout (10 ops)") ok
time (mean ± σ)         range (min … max) 
   7.88 µs ±   0.04 µs     7.82 µs …   7.95 µs  in 10 ×  12334 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:29 ("branch - checkout (100 ops)") ok
time (mean ± σ)         range (min … max) 
 109.13 µs ±   0.65 µs   108.12 µs … 109.92 µs  in 10 ×    915 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:47 ("branch - checkout (1000 ops)") ok
time (mean ± σ)         range (min … max) 
   1.88 ms ±  23.16 µs     1.86 ms …   1.93 ms  in 10 ×     54 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:65 ("branch - advance (10 new ops)") ok
time (mean ± σ)         range (min … max) 
  36.68 µs ±   0.21 µs    36.48 µs …  37.19 µs  in 10 ×   2740 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:91 ("branch - advance (100 new ops)") ok
time (mean ± σ)         range (min … max) 
 154.04 µs ±   1.86 µs   151.52 µs … 158.41 µs  in 10 ×    660 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:117 ("branch - checkout with concurrent branches") ok
time (mean ± σ)         range (min … max) 
 111.07 µs ±   0.71 µs   110.14 µs … 112.08 µs  in 10 ×    908 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:145 ("branch - checkout with deletes") ok
time (mean ± σ)         range (min … max) 
 169.31 µs ±   0.83 µs   167.82 µs … 170.39 µs  in 10 ×    591 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) 
 136.96 µs ±   0.20 µs   136.63 µs … 137.29 µs  in 10 ×    730 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) 
  17.57 ms ±   4.18 ms    11.81 ms …  23.16 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) 
  10.98 µs ±   0.07 µs    10.91 µs …  11.11 µs  in 10 ×   9052 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:264 ("branch - to_text (1000 chars)") ok
time (mean ± σ)         range (min … max) 
 112.65 µs ±   0.40 µs   112.20 µs … 113.30 µs  in 10 ×    879 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.09 µs ±   0.03 µs    27.03 µs …  27.14 µs  in 10 ×   3697 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:311 ("branch - single advance (50 new ops)") ok
time (mean ± σ)         range (min … max) 
  85.93 µs ±   0.97 µs    84.83 µs …  88.16 µs  in 10 ×   1174 runs
[dowdiness/event-graph-walker] bench internal/branch/branch_benchmark.mbt:342 ("branch - realistic typing (50 chars)") ok
time (mean ± σ)         range (min … max) 
  80.66 ms ±  21.34 ms    50.96 ms … 110.78 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) 
  24.69 µs ±   0.26 µs    24.48 µs …  25.22 µs  in 10 ×   4078 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.78 µs ±   0.05 µs     4.69 µs …   4.84 µs  in 10 ×  20643 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.23 µs ±   1.47 µs    77.50 µs …  82.41 µs  in 10 ×   1229 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 ±  17.89 µs     1.39 ms …   1.44 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) 
  78.96 µs ±   0.08 µs    78.83 µs …  79.07 µs  in 10 ×   1271 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.24 µs ±   0.19 µs    76.94 µs …  77.56 µs  in 10 ×   1298 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.25 µs ±   0.35 µs   149.87 µs … 151.08 µs  in 10 ×    665 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.36 µs ±   0.08 µs    32.24 µs …  32.48 µs  in 10 ×   3076 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.19 µs ±   0.14 µs    61.94 µs …  62.40 µs  in 10 ×   1607 runs
[dowdiness/event-graph-walker] bench internal/causal_graph/walker_benchmark.mbt:176 ("walker - large history (10000 ops)") ok
time (mean ± σ)         range (min … max) 
  23.37 ms ± 549.39 µs    22.64 ms …  24.30 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) 
 656.12 ms ±  56.65 ms   593.38 ms … 744.85 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) 
 699.34 ms ±  65.69 ms   613.37 ms … 812.54 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.52 µs ±   0.00 µs     1.51 µs …   1.52 µs  in 10 ×  65981 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.61 µs ±   0.00 µs     0.61 µ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.36 µs …   2.38 µs  in 10 ×  42023 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.30 µs ±   0.00 µs     1.29 µs …   1.30 µs  in 10 ×  77395 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.93 µs ±   0.04 µs    17.87 µs …  17.99 µs  in 10 ×   5571 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.24 µs ±   0.05 µs    18.18 µs …  18.33 µs  in 10 ×   5481 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.04 µ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/fugue/jump_ancestors_benchmark.mbt:2 ("jump - sequential append 1K") ok
time (mean ± σ)         range (min … max) 
 154.44 µs ±   0.97 µs   153.36 µs … 156.28 µs  in 10 ×    645 runs
[dowdiness/event-graph-walker] bench internal/fugue/jump_ancestors_benchmark.mbt:26 ("jump - concurrent insert into 1K doc") ok
time (mean ± σ)         range (min … max) 
 151.82 µs ±   0.48 µs   151.17 µs … 152.45 µs  in 10 ×    659 runs
[dowdiness/event-graph-walker] bench internal/fugue/jump_ancestors_benchmark.mbt:58 ("jump - concurrent insert into 10K doc") ok
time (mean ± σ)         range (min … max) 
   4.24 ms ±  87.71 µs     4.12 ms …   4.40 ms  in 10 ×     24 runs
[dowdiness/event-graph-walker] bench internal/fugue/jump_ancestors_benchmark.mbt:90 ("jump - degenerate chain remote insert") ok
time (mean ± σ)         range (min … max) 
 151.51 µs ±   0.44 µs   150.89 µs … 152.01 µs  in 10 ×    660 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:6 ("oplog - insert (100 ops)") ok
time (mean ± σ)         range (min … max) 
  64.36 µs ±   1.60 µs    63.32 µs …  68.38 µs  in 10 ×   1524 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:20 ("oplog - insert (1000 ops)") ok
time (mean ± σ)         range (min … max) 
 818.11 µs ±   6.15 µs   809.55 µs … 830.31 µ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.65 µs ±   0.17 µs    97.41 µs …  97.95 µs  in 10 ×   1026 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:58 ("oplog - apply_remote (50 ops)") ok
time (mean ± σ)         range (min … max) 
  37.98 µs ±   0.07 µs    37.88 µs …  38.08 µs  in 10 ×   2635 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.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.03 µ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.65 µs ±   0.76 µs    87.98 µs …  89.71 µs  in 10 ×   1113 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:150 ("oplog - walk_and_collect (concurrent)") ok
time (mean ± σ)         range (min … max) 
  92.25 µs ±   0.65 µs    91.06 µs …  92.95 µs  in 10 ×   1096 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.77 µs ±   0.21 µs    42.54 µs …  43.11 µs  in 10 ×   2313 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:202 ("oplog - walk_filtered (inserts only)") ok
time (mean ± σ)         range (min … max) 
  62.91 µs ±   0.24 µs    62.59 µs …  63.24 µs  in 10 ×   1593 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:233 ("oplog - sequential typing (500 chars)") ok
time (mean ± σ)         range (min … max) 
 366.31 µs ±   1.05 µs   364.51 µs … 367.84 µ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) 
  64.07 µs ±   0.27 µs    63.74 µs …  64.54 µs  in 10 ×   1561 runs
[dowdiness/event-graph-walker] bench internal/oplog/oplog_benchmark.mbt:270 ("oplog - sequential typing (100000 chars)") ok
time (mean ± σ)         range (min … max) 
 266.70 ms ±  33.43 ms   208.80 ms … 301.85 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) 
 724.70 ms ±  29.96 ms   694.81 ms … 784.51 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) 
  86.26 µs ±   0.15 µs    85.95 µs …  86.47 µs  in 10 ×   1098 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:22 ("text - insert append (1000 chars)") ok
time (mean ± σ)         range (min … max) 
   1.17 ms ±   6.24 µs     1.16 ms …   1.18 ms  in 10 ×     85 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:35 ("text - insert prepend (100 chars)") ok
time (mean ± σ)         range (min … max) 
 878.23 µs ±   5.19 µs   871.31 µs … 888.81 µs  in 10 ×    112 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) 
 262.37 µs ±   0.64 µs   261.30 µs … 263.26 µs  in 10 ×    376 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:70 ("text - text() (100-char doc)") ok
time (mean ± σ)         range (min … max) 
  13.09 µs ±   0.04 µs    13.02 µs …  13.14 µs  in 10 ×   7659 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:83 ("text - text() (1000-char doc)") ok
time (mean ± σ)         range (min … max) 
 135.22 µs ±   0.96 µs   133.98 µs … 136.91 µs  in 10 ×    740 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.12 µ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) 
 554.01 µs ±   4.01 µs   549.97 µs … 563.58 µs  in 10 ×    179 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:156 ("text - sync apply (50 remote ops)") ok
time (mean ± σ)         range (min … max) 
 101.38 µs ±   0.25 µs   101.02 µs … 101.82 µs  in 10 ×    975 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:171 ("text - sync apply (500 remote ops)") ok
time (mean ± σ)         range (min … max) 
   1.50 ms ±  10.60 µs     1.48 ms …   1.51 ms  in 10 ×     67 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) 
 210.99 µs ±   1.12 µs   209.99 µs … 213.51 µs  in 10 ×    473 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:216 ("text - checkout (midpoint of 100-op doc)") ok
time (mean ± σ)         range (min … max) 
  49.14 µs ±   0.17 µs    48.92 µs …  49.38 µs  in 10 ×   2034 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:233 ("text - checkout (midpoint of 1000-op doc)") ok
time (mean ± σ)         range (min … max) 
 811.06 µs ±   5.59 µs   807.09 µs … 826.26 µs  in 10 ×    124 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.10 µs ±   0.01 µs     2.09 µs …   2.11 µs  in 10 ×  47437 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.40 µs …   2.47 µs  in 10 ×  40956 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:281 ("text - undo undo() (10-op group)") ok
time (mean ± σ)         range (min … max) 
  29.88 µs ±   0.09 µs    29.78 µs …  30.03 µs  in 10 ×   3345 runs
[dowdiness/event-graph-walker] bench text/text_benchmark.mbt:295 ("text - undo undo() (50-op group)") ok
time (mean ± σ)         range (min … max) 
 448.42 µs ±   1.56 µs   445.67 µs … 450.63 µs  in 10 ×    223 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) 
  35.79 µs ±   0.06 µs    35.70 µs …  35.88 µs  in 10 ×   2784 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) 
 322.93 µs ±   0.97 µs   321.53 µs … 324.47 µs  in 10 ×    310 runs
Total tests: 91, passed: 91, failed: 0.

Benchmarks run with --release flag

- reconcile[T]: remove unnecessary Eq bound (only uses TreeNode::same_kind)
- reconcile_children[T]: same
- from_term_node_impl[T]: TreeNode+Renderable → Renderable (only uses label)
- can_skip_subtree[T]: remove unnecessary TreeNode bound
- interactive_node_stamp[T]: already fixed in prior commit

2 remaining MoonBit warnings are false positives on transitively-used bounds.

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

🧹 Nitpick comments (5)
projection/tree_editor_model.mbt (1)

194-203: is_valid_drop still uses concrete @ast.Term while surrounding model is generic.

This function takes @ast.Term parameters directly, which is inconsistent with the otherwise fully parameterized TreeEditorState[T] model. This may be intentional for Phase 1 (keeping lambda-specific behavior), but consider noting this as a Phase 2 candidate for generalization.

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

In `@projection/tree_editor_model.mbt` around lines 194 - 203, is_valid_drop
currently takes concrete `@ast.Term` parameters which conflicts with the generic
TreeEditorState[T] model; change the function signature to accept the generic
element type (e.g. T or TreeEditorState[T]’s element type) instead of `@ast.Term`,
update any internal references and callers to use that generic type, and if you
want to keep lambda-specific behavior for Phase 1 add a TODO note to generalize
later; refer to the is_valid_drop function, the `@ast.Term` type, DropPosition,
and the TreeEditorState[T] model when making these edits.
projection/proj_node.mbt (1)

7-15: Update the ProjNode docs to match the new generic type.

The surrounding header still describes this as a Term-only node, but the struct now exposes kind : T. That mismatch will show up in generated docs and makes the new framework API look narrower than it is.

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

In `@projection/proj_node.mbt` around lines 7 - 15, The doc comment above the
ProjNode struct still says it's a Term-only node; update that header to reflect
the new generic type parameter T and that ProjNode now holds kind : T (a generic
kind) rather than a Term, e.g. describe it as a generic projection node
parameterized by T and mention the kind and children fields (ProjNode[T], kind :
T, children : Array[ProjNode[T]]) so generated docs correctly represent the API.
projection/reconcile_ast.mbt (1)

5-9: Drop the unused Eq bound from the generic reconciliation API.

Line 5 and Line 40 only rely on T::same_kind(...); nothing in this file compares T values. Keeping + Eq on the new API makes custom AST adapters implement equality for no functional reason, which unnecessarily narrows the framework surface.

Also applies to: 40-44

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

In `@projection/reconcile_ast.mbt` around lines 5 - 9, The generic bound on
reconcile is overly restrictive: remove the unused Eq constraint from the
signature pub fn[T : TreeNode + Eq] reconcile(...) -> ProjNode[T] (and the
analogous occurrence around lines 40–44) so it becomes pub fn[T : TreeNode]
reconcile(...); ensure any other declarations or helper functions in this file
that mirror the same generic signature are updated similarly and run tests to
confirm nothing else relied on Eq.
projection/text_edit_wrap.mbt (1)

128-165: Make InsertChild's upper-bound behavior explicit.

Line 134 rejects negative indices, but the Module path later treats any oversized index as “insert before body.” That silent clamping is easy for callers to miss. Either validate the upper bound too, or document that large indices intentionally append at the module tail.

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

In `@projection/text_edit_wrap.mbt` around lines 128 - 165, The function
compute_insert_child currently only rejects negative indices but silently treats
index >= parent_node.children.length() in the `@ast.Term`::Module branch as
"insert before body", which is ambiguous; update compute_insert_child to make
upper-bound behavior explicit by adding an index-range check after obtaining
parent_node (e.g., compare index against parent_node.children.length()) and
either (a) return an Err("Index out of bounds") for invalid large indices, or
(b) explicitly handle append-by-index (documented) by using a clear branch for
index == parent_node.children.length() and only error when index >
parent_node.children.length(); adjust the Module branch (and any other
kind-specific branches) accordingly so parent_node.children and
source_map.get_range usage is guarded by the validated index.
editor/sync_editor.mbt (1)

26-41: SyncEditor::new is now coupled to the memo graph internals.

This constructor now bakes Runtime, Signal, Memo, and the exact 4-value memo tuple into the public API. That makes every non-lambda backend depend on the current projection wiring, so any future reshaping of the memo pipeline will force another external API change. Consider taking a single backend/factory record instead, and keep the low-level memo graph behind that boundary.

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

In `@editor/sync_editor.mbt` around lines 26 - 41, SyncEditor::new currently
exposes memo-graph types (Runtime, Signal, Memo) and a fixed 4-tuple return
shape via the build_memos parameter, coupling callers to projection internals;
instead create and accept a single backend/factory record (e.g., a BuildBackend
or SyncBackend factory type) with a single method (e.g., build_memos or
create_backend) that encapsulates the Runtime/Signal/Memo usage and returns a
backend object or record that exposes only the needed high-level operations
(like get_proj_node, get_projection_map, source_map) so callers of
SyncEditor::new only depend on make_parser, agent_id, and the new backend
factory type (leave capture_timeout_ms unchanged). Update the SyncEditor::new
signature to replace the multi-typed build_memos parameter with this single
factory/record type and change internal usage to call the factory method to
obtain the opaque backend; keep existing internal memo wiring private to the
implementation (refer to SyncEditor::new, make_parser, build_memos,
capture_timeout_ms, and any helpers that currently destructure the 4-tuple).
🤖 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/projection_memo.mbt`:
- Around line 18-20: The empty-buffer reset path currently clears
prev_flat_proj_ref and prev_syntax_root_ref but leaves prev_module_id_ref
intact, causing stale NodeId to be reused by to_proj_node_with_prev_module_id;
update the same reset/clear logic (the block that resets prev_flat_proj_ref,
prev_syntax_root_ref and next_id_ref) to also clear prev_module_id_ref (set it
to None/empty) so the previous module id cannot be attached to newly typed
content; ensure you apply the same change in both places mentioned (the initial
block around prev_flat_proj_ref/prev_syntax_root_ref/next_id_ref and the
analogous block referenced at lines 114-137).

In `@pkg.generated.mbti`:
- Line 10: The exported function apply_sync_json now returns String (not void)
which is intentional but undocumented; update documentation and release notes to
call out this API change and add a migration note showing callers (including
examples/ideal/main re-export wrappers) how to handle the returned String, and
update JS_INTEGRATION.md to reflect the new return type and any necessary
TypeScript/consumer-side changes.

In `@projection/tree_editor_refresh.mbt`:
- Around line 101-105: The current fast-path compares proj_node.kind and
prev_node.kind with full T equality which can trigger deep subtree comparisons;
change the checks in refresh_node_minimal (where proj_node.kind !=
prev_node.kind and where you compare labels via Renderable::label) to use a
local shape/render key instead: call TreeNode::same_kind (or compare
Renderable::kind_tag plus the local label via Renderable::label) to decide
kind-equality, and leave descendant equality to the recursive child walk; apply
the same replacement for the other occurrence that mirrors this logic.

---

Nitpick comments:
In `@editor/sync_editor.mbt`:
- Around line 26-41: SyncEditor::new currently exposes memo-graph types
(Runtime, Signal, Memo) and a fixed 4-tuple return shape via the build_memos
parameter, coupling callers to projection internals; instead create and accept a
single backend/factory record (e.g., a BuildBackend or SyncBackend factory type)
with a single method (e.g., build_memos or create_backend) that encapsulates the
Runtime/Signal/Memo usage and returns a backend object or record that exposes
only the needed high-level operations (like get_proj_node, get_projection_map,
source_map) so callers of SyncEditor::new only depend on make_parser, agent_id,
and the new backend factory type (leave capture_timeout_ms unchanged). Update
the SyncEditor::new signature to replace the multi-typed build_memos parameter
with this single factory/record type and change internal usage to call the
factory method to obtain the opaque backend; keep existing internal memo wiring
private to the implementation (refer to SyncEditor::new, make_parser,
build_memos, capture_timeout_ms, and any helpers that currently destructure the
4-tuple).

In `@projection/proj_node.mbt`:
- Around line 7-15: The doc comment above the ProjNode struct still says it's a
Term-only node; update that header to reflect the new generic type parameter T
and that ProjNode now holds kind : T (a generic kind) rather than a Term, e.g.
describe it as a generic projection node parameterized by T and mention the kind
and children fields (ProjNode[T], kind : T, children : Array[ProjNode[T]]) so
generated docs correctly represent the API.

In `@projection/reconcile_ast.mbt`:
- Around line 5-9: The generic bound on reconcile is overly restrictive: remove
the unused Eq constraint from the signature pub fn[T : TreeNode + Eq]
reconcile(...) -> ProjNode[T] (and the analogous occurrence around lines 40–44)
so it becomes pub fn[T : TreeNode] reconcile(...); ensure any other declarations
or helper functions in this file that mirror the same generic signature are
updated similarly and run tests to confirm nothing else relied on Eq.

In `@projection/text_edit_wrap.mbt`:
- Around line 128-165: The function compute_insert_child currently only rejects
negative indices but silently treats index >= parent_node.children.length() in
the `@ast.Term`::Module branch as "insert before body", which is ambiguous; update
compute_insert_child to make upper-bound behavior explicit by adding an
index-range check after obtaining parent_node (e.g., compare index against
parent_node.children.length()) and either (a) return an Err("Index out of
bounds") for invalid large indices, or (b) explicitly handle append-by-index
(documented) by using a clear branch for index == parent_node.children.length()
and only error when index > parent_node.children.length(); adjust the Module
branch (and any other kind-specific branches) accordingly so
parent_node.children and source_map.get_range usage is guarded by the validated
index.

In `@projection/tree_editor_model.mbt`:
- Around line 194-203: is_valid_drop currently takes concrete `@ast.Term`
parameters which conflicts with the generic TreeEditorState[T] model; change the
function signature to accept the generic element type (e.g. T or
TreeEditorState[T]’s element type) instead of `@ast.Term`, update any internal
references and callers to use that generic type, and if you want to keep
lambda-specific behavior for Phase 1 add a TODO note to generalize later; refer
to the is_valid_drop function, the `@ast.Term` type, DropPosition, and the
TreeEditorState[T] model when making these edits.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 459a739c-95aa-469f-8417-5221f08d22d9

📥 Commits

Reviewing files that changed from the base of the PR and between 386e6eb and 8e1b192.

📒 Files selected for processing (56)
  • crdt.mbt
  • crdt_test.mbt
  • crdt_undo.mbt
  • editor/error_path_wbtest.mbt
  • editor/performance_benchmark.mbt
  • editor/pkg.generated.mbti
  • editor/projection_memo.mbt
  • editor/sync_editor.mbt
  • editor/sync_editor_parser.mbt
  • editor/sync_editor_parser_test.mbt
  • editor/sync_editor_sync.mbt
  • editor/sync_editor_test.mbt
  • editor/sync_editor_text.mbt
  • editor/sync_editor_text_wbtest.mbt
  • editor/sync_editor_undo.mbt
  • editor/sync_editor_undo_test.mbt
  • editor/sync_editor_ws.mbt
  • editor/sync_editor_ws_wbtest.mbt
  • editor/test_error_recovery.mbt
  • editor/tree_edit_bridge.mbt
  • editor/tree_edit_bridge_test.mbt
  • examples/ideal/main/action_model.mbt
  • examples/ideal/main/model.mbt
  • examples/rabbita/main/benchmark_support.mbt
  • examples/rabbita/main/main.mbt
  • integration_ws_test.mbt
  • moon.pkg
  • pkg.generated.mbti
  • projection/flat_proj.mbt
  • projection/pkg.generated.mbti
  • projection/proj_node.mbt
  • projection/proj_node_json.mbt
  • projection/reconcile_ast.mbt
  • projection/scope.mbt
  • projection/scope_wbtest.mbt
  • projection/source_map.mbt
  • 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
  • projection/traits.mbt
  • projection/traits_term.mbt
  • projection/tree_editor.mbt
  • projection/tree_editor_model.mbt
  • projection/tree_editor_refresh.mbt
  • projection/tree_editor_wbtest.mbt
  • projection/tree_lens.mbt
  • projection/tree_refresh_benchmark.mbt

Comment on lines +18 to +20
let prev_flat_proj_ref : Ref[@proj.FlatProj?] = Ref::new(None)
let prev_syntax_root_ref : Ref[@seam.SyntaxNode?] = Ref::new(None)
let next_id_ref : Ref[Int] = Ref::new(0)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Clear prev_module_id_ref when the document is explicitly emptied.

The empty-buffer reset path drops the previous projection state but leaves prev_module_id_ref alive. After a full clear/retype cycle, to_proj_node_with_prev_module_id(...) will attach the old root NodeId to unrelated content, so NodeId-keyed UI state can bleed into the new tree.

🔧 Suggested fix
+  let prev_module_id_ref : Ref[Int?] = Ref::new(None)
   let prev_flat_proj_ref : Ref[`@proj.FlatProj`?] = Ref::new(None)
   let prev_syntax_root_ref : Ref[`@seam.SyntaxNode`?] = Ref::new(None)
   let next_id_ref : Ref[Int] = Ref::new(0)
...
       if source_text.get() == "" {
         prev_flat_proj_ref.val = None
         prev_syntax_root_ref.val = None
+        prev_module_id_ref.val = None
         changed_def_indices_ref.val = None
         return None
       }
...
-  let prev_module_id_ref : Ref[Int?] = Ref::new(None)

Also applies to: 114-137

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

In `@editor/projection_memo.mbt` around lines 18 - 20, The empty-buffer reset path
currently clears prev_flat_proj_ref and prev_syntax_root_ref but leaves
prev_module_id_ref intact, causing stale NodeId to be reused by
to_proj_node_with_prev_module_id; update the same reset/clear logic (the block
that resets prev_flat_proj_ref, prev_syntax_root_ref and next_id_ref) to also
clear prev_module_id_ref (set it to None/empty) so the previous module id cannot
be attached to newly typed content; ensure you apply the same change in both
places mentioned (the initial block around
prev_flat_proj_ref/prev_syntax_root_ref/next_id_ref and the analogous block
referenced at lines 114-137).


// Values
pub fn apply_sync_json(Int, String) -> Unit
pub fn apply_sync_json(Int, String) -> String
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

🧩 Analysis chain

🏁 Script executed:

# Find the pkg.generated.mbti file
find . -name "pkg.generated.mbti" -type f

Repository: dowdiness/canopy

Length of output: 368


🏁 Script executed:

# Check the current state of the file
if [ -f "pkg.generated.mbti" ]; then
  cat -n pkg.generated.mbti
fi

Repository: dowdiness/canopy

Length of output: 3108


🏁 Script executed:

# Look for git history of changes to this file
git log --oneline -n 20 -- "pkg.generated.mbti" 2>/dev/null || echo "No git history available"

Repository: dowdiness/canopy

Length of output: 131


🏁 Script executed:

# Check for any recent diffs or changes related to apply_sync_json
git diff HEAD~10..HEAD -- "pkg.generated.mbti" 2>/dev/null || echo "Unable to retrieve git diff"

Repository: dowdiness/canopy

Length of output: 87


🏁 Script executed:

# Search for apply_sync_json in the codebase to understand its usage
rg "apply_sync_json" -B 2 -A 2

Repository: dowdiness/canopy

Length of output: 16518


🏁 Script executed:

# Search for any compatibility wrappers or version documentation
rg "apply_sync_json|version|compat" --type mbti -B 1 -A 1

Repository: dowdiness/canopy

Length of output: 88


API return-type change is intentional but lacks explicit documentation.

apply_sync_json returning String is intentional and aligns with the actual implementation and TypeScript usage expectations. However, consumers in dependent packages (e.g., examples/ideal/main) use re-export wrappers to manage this change. Ensure this change is explicitly documented in release notes or a migration guide, especially since older documentation (JS_INTEGRATION.md) still references void.

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

In `@pkg.generated.mbti` at line 10, The exported function apply_sync_json now
returns String (not void) which is intentional but undocumented; update
documentation and release notes to call out this API change and add a migration
note showing callers (including examples/ideal/main re-export wrappers) how to
handle the returned String, and update JS_INTEGRATION.md to reflect the new
return type and any necessary TypeScript/consumer-side changes.

dowdiness and others added 4 commits March 24, 2026 22:12
Allows external packages to implement these traits for their own AST types.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- proj_node.mbt: update doc comment to reflect generic ProjNode[T]
- tree_editor_refresh.mbt: replace deep T equality in can_skip_subtree
  with TreeNode::same_kind + Renderable::label (avoids O(subtree)
  comparison on complex AST types)

Verified not a bug: prev_module_id_ref scoping is intentional (kept
across transient projection misses for stable Module IDs).
reconcile Eq bound already fixed in prior commit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove unused TreeNode bounds from from_term_node, from_term_node_with_state
  (they only call Renderable::label via from_term_node_impl)
- Add explicit type annotation TreeEditorState[@ast.Term] in test to
  resolve unresolved type variable warning
- CI uses -w @A (warnings as errors), so these were blocking

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add [@ast.Term] type parameter to TreeEditorState, InteractiveTreeNode
  in examples/rabbita/ and examples/ideal/
- Add dowdiness/lambda/ast import to Rabbita's moon.pkg

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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
projection/reconcile_ast.mbt (1)

55-84: ⚠️ Potential issue | 🟠 Major

same_kind is too weak for the child LCS key.

At Line 57 and Line 76, the DP only distinguishes children by T::same_kind. With the current @ast.Term impl in projection/traits_term.mbt:23-37, every Var(_) matches every other Var(_) and every Bop(_, _, _) matches every other Bop(_, _, _). A middle insertion like [x, y] -> [x, z, y] therefore backtracks as x -> z, y -> y, so the new z inherits x's node_id and the unchanged x gets a fresh one. That breaks identity preservation for same-tag sibling lists. Please tighten the child-match predicate here, e.g. same_kind + Renderable::label, or a dedicated stable-match key.

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

In `@projection/reconcile_ast.mbt` around lines 55 - 84, The LCS child-matching
currently uses only T::same_kind in the DP fill and backtrack (the comparisons
in the loops referencing old_children[i - 1].kind and new_children[j - 1].kind),
which is too weak and causes wrong identity preservation; update both
comparisons (the if in the dp fill and the if in the backtracking that sets
old_matched/new_matched) to use a stronger stable-match key instead of just
T::same_kind — for example combine T::same_kind with Renderable::label (or call
a new stable_match_key(old_children[i-1], new_children[j-1]) helper) so siblings
with the same tag but different labels don’t erroneously match and node_id
assignments stay stable.
🧹 Nitpick comments (1)
projection/tree_editor_refresh.mbt (1)

235-238: Consider simplifying boolean initialization.

This pattern can be condensed using the is pattern:

♻️ Optional simplification
-    let mut children_reused = match previous_loaded_children {
-      Some(_) => true
-      None => false
-    }
+    let mut children_reused = previous_loaded_children is Some(_)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projection/tree_editor_refresh.mbt` around lines 235 - 238, The boolean
initialization using a match on previous_loaded_children can be simplified:
replace the match that assigns children_reused with a direct check using
previous_loaded_children.is_some() (or the `is` pattern equivalent) so
children_reused is set directly from the Option presence; update the assignment
where children_reused is defined to use this shorter idiom referencing
previous_loaded_children.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@projection/reconcile_ast.mbt`:
- Around line 55-84: The LCS child-matching currently uses only T::same_kind in
the DP fill and backtrack (the comparisons in the loops referencing
old_children[i - 1].kind and new_children[j - 1].kind), which is too weak and
causes wrong identity preservation; update both comparisons (the if in the dp
fill and the if in the backtracking that sets old_matched/new_matched) to use a
stronger stable-match key instead of just T::same_kind — for example combine
T::same_kind with Renderable::label (or call a new
stable_match_key(old_children[i-1], new_children[j-1]) helper) so siblings with
the same tag but different labels don’t erroneously match and node_id
assignments stay stable.

---

Nitpick comments:
In `@projection/tree_editor_refresh.mbt`:
- Around line 235-238: The boolean initialization using a match on
previous_loaded_children can be simplified: replace the match that assigns
children_reused with a direct check using previous_loaded_children.is_some() (or
the `is` pattern equivalent) so children_reused is set directly from the Option
presence; update the assignment where children_reused is defined to use this
shorter idiom referencing previous_loaded_children.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8562df42-2ae9-4402-8eda-a944ea6a4d42

📥 Commits

Reviewing files that changed from the base of the PR and between 8e1b192 and 2b4e6e6.

📒 Files selected for processing (9)
  • projection/lens_test.mbt
  • projection/pkg.generated.mbti
  • projection/proj_node.mbt
  • projection/reconcile_ast.mbt
  • projection/traits.mbt
  • projection/traits_term.mbt
  • projection/tree_editor_model.mbt
  • projection/tree_editor_refresh.mbt
  • projection/tree_editor_wbtest.mbt
✅ Files skipped from review due to trivial changes (1)
  • projection/traits.mbt
🚧 Files skipped from review as they are similar to previous changes (4)
  • projection/traits_term.mbt
  • projection/pkg.generated.mbti
  • projection/proj_node.mbt
  • projection/tree_editor_wbtest.mbt

dowdiness and others added 5 commits March 24, 2026 23:45
InteractiveNodeStamp was storing kind: T, causing deep O(subtree)
equality comparison via derive(Eq) on @ast.Term. Replaced with
kind_tag: String (from Renderable::kind_tag) for O(1) comparison.

Deep child changes are caught by the recursive child walk in
can_skip_subtree and refresh_node_minimal, not by stamp equality.

Also removed now-unnecessary Eq bounds from TreeEditorState methods
(from_projection, refresh, expand_node, hydrate_subtree).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MoonBit deprecated .is_some() — CI treats deprecation as error.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The package-level import of dowdiness/lambda/ast in moon.pkg requires
the module-level dependency in moon.mod.json. Without it, Cloudflare
Workers build fails with "module is not imported" error.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cloudflare Workers build uses a MoonBit version that doesn't support
the Type[Param]::method() call syntax. Use type annotation on the
let binding instead: `let x : Type[Param] = Type::method(...)`.

Affects: examples/rabbita/, examples/ideal/

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@dowdiness dowdiness merged commit 9e04d2f into main Mar 24, 2026
16 checks passed
@dowdiness dowdiness deleted the feature/framework-extraction branch March 24, 2026 15:23
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