Skip to content

fix(editor): add list numbering watcher#14414

Open
asehee wants to merge 5 commits intotoeverything:canaryfrom
asehee:fix/list-numbering-watcher
Open

fix(editor): add list numbering watcher#14414
asehee wants to merge 5 commits intotoeverything:canaryfrom
asehee:fix/list-numbering-watcher

Conversation

@asehee
Copy link
Copy Markdown
Contributor

@asehee asehee commented Feb 11, 2026

Summary

This PR adds a watcher extension to keep numbered list ordering consistent in list blocks.

Changes

  • Added ListNumberingWatcherExtension at blocksuite/affine/blocks/list/src/list-numbering-watcher.ts.
  • Registered the extension in blocksuite/affine/blocks/list/src/store.ts.

Behavior

The watcher updates numbering for affected continuous numbered siblings when:

  • a numbered list item is deleted
  • a list item type changes from numbered to a non-numbered type

Implementation Notes

  • Scope is limited to affine:list events only.
  • Guard clauses are used to avoid unrelated updates.
  • Existing order-correction utility is reused where applicable.
  • Multi-item renumbering is applied in a transaction to keep updates atomic.

Validation

  • Type check passed for list block project config.
  • Lint/format checks passed via commit hooks.

Summary by CodeRabbit

  • New Features

    • Automatic numbered list renumbering: numbered lists now automatically maintain continuous sequencing when items are added, converted, or removed, preserving correct order across edits.
    • Real‑time monitoring of list changes to keep numbering synchronized.
  • Stability

    • Improved reliability of numbering updates to reduce mismatches and ensure consistent behavior during edits and deletions.

@asehee asehee requested a review from a team as a code owner February 11, 2026 04:45
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 11, 2026

📝 Walkthrough

Walkthrough

New Store extension ListNumberingWatcherExtension watches affine:list blocks, caches sibling/type relationships, subscribes to add/update/delete/propsUpdated events, and renumbers subsequent numbered list items within transactions to preserve continuous numbering.

Changes

Cohort / File(s) Summary
List Numbering Extension
blocksuite/affine/blocks/list/src/list-numbering-watcher.ts
Adds ListNumberingWatcherExtension. Implements caches for next-sibling and previous-type, subscribes to affine:list events, and performs transactional renumbering on additions, type changes, and deletions.
List Store Registration
blocksuite/affine/blocks/list/src/store.ts
Imports and registers ListNumberingWatcherExtension in ListStoreExtension.setup, activating the extension during store lifecycle.

Sequence Diagram

sequenceDiagram
    actor Store as ListStore
    participant Ext as ListNumberingWatcherExtension
    participant Cache as Cache
    participant Blocks as affine:list Blocks
    participant Txn as Store Transaction

    Store->>Ext: loaded()
    Ext->>Blocks: scan existing affine:list blocks
    Ext->>Cache: cache next-sibling & type

    alt Numbered List Added
        Blocks->>Ext: blockAdded
        Ext->>Cache: add cache entries
        Ext->>Blocks: subscribe to propsUpdated
    end

    alt Type Changed (Numbered → Other)
        Blocks->>Ext: propsUpdated (type changed)
        Ext->>Cache: find next numbered sibling
        Ext->>Txn: begin transaction
        Txn->>Blocks: renumber subsequent numbered items
        Txn->>Ext: commit
    end

    alt Numbered List Deleted
        Blocks->>Ext: blockDeleted
        Ext->>Cache: lookup next numbered item
        Ext->>Txn: begin transaction
        Txn->>Blocks: renumber from next item onward
        Txn->>Ext: commit
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I hop through lists both near and far,
I track each sibling, number every star.
When types change or a block takes flight,
I stitch the order back just right.
Hooray — tidy counts by soft moonlight ✨

🚥 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 'fix(editor): add list numbering watcher' directly and clearly describes the main change: introducing a new extension to maintain numbered list ordering.
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 docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
blocksuite/affine/blocks/list/src/list-numbering-watcher.ts (1)

69-71: Subscriptions for deleted blocks accumulate in disposableGroup.

Each call to _subscribeToModelChanges adds a subscription to this.store.disposableGroup, but when a block is deleted, its subscription is never individually removed. The closure captures the model reference, preventing the WeakMap caches from garbage-collecting entries for deleted models.

For long-lived stores with frequent list add/delete cycles, dead subscriptions and their captured references accumulate. Consider tracking per-model disposables in a Map<string, Disposable> and cleaning them up in _handleDeleteEvent.

Also applies to: 202-208


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.

Copy link
Copy Markdown
Contributor

@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

🤖 Fix all issues with AI agents
In `@blocksuite/affine/blocks/list/src/list-numbering-watcher.ts`:
- Around line 54-111: The propsUpdated handler inside _subscribeToModelChanges
duplicates the numbered→non-numbered renumbering logic already handled by the
blockUpdated 'update' handler (which uses correctNumberedListsOrderToPrev),
causing inconsistent behavior; remove the renumbering block in
_subscribeToModelChanges (the code that reads previousType, nextSiblingId,
fetches nextSibling, calls _getPrevNumberedSibling and wraps manual transact
renumbering) and instead only update caches (_previousTypeCache and
_nextSiblingCache) there, leaving all actual renumbering to the blockUpdated
'update' path (ensure _previousTypeCache is still set and _nextSiblingCache
updated when currentType === 'numbered').
- Around line 113-129: The _getPrevNumberedSibling function currently walks past
non-numbered siblings and can cross list-group boundaries; either remove this
unused method if duplicate-handler consolidation eliminated its callers, or
change its logic (inside _getPrevNumberedSibling using this.store.getPrev and
matchModels/ListBlockModel checks) to stop at the first sibling that is not a
numbered ListBlockModel (i.e., if a previous sibling exists but is not
matchModels(... && props.type === 'numbered'), return null instead of continuing
to the next previous sibling) so numbering never crosses group boundaries.
- Around line 223-265: The delete handler in ListNumberingWatcher leaves stale
entries in _nextSiblingCache for the previous sibling when a middle numbered
item is deleted; update the cache so previous sibling points to the deleted
item's next sibling (or null if none). In the delete branch that checks
payload.model, after determining the deletedIndex via
this.store.getParent(payload.model), find the previous sibling
(parent.children[deletedIndex - 1]) and set
this._nextSiblingCache.set(prevSibling, nextSiblingId ?? null); also handle the
case where a cache hit yields a missing block (getBlock(nextSiblingId) returns
undefined) by recomputing nextSiblingId from the parent and updating
_nextSiblingCache before deciding whether to call
correctNumberedListsOrderToPrev(this.store, nextSibling).
🧹 Nitpick comments (2)
blocksuite/affine/blocks/list/src/list-numbering-watcher.ts (2)

36-52: Non-numbered list items are not cached in _previousTypeCache during initialization.

During _initializeCache, only numbered items have their type stored in _previousTypeCache (line 46). All other list types (bulleted, todo, toggle) are subscribed to propsUpdated (line 50) but their initial type isn't cached. This means _previousTypeCache.get(model) returns undefined for these items until their first type change.

Currently this is safe because the only check is previousType === 'numbered', but it's fragile — a future change adding logic for other transitions would silently fail.

Suggested fix
       if (model.props.type === 'numbered') {
         const nextSibling = this.store.getNext(model);
         this._nextSiblingCache.set(model, nextSibling?.id ?? null);
-        this._previousTypeCache.set(model, 'numbered');
       }
+      // Always cache the type so transitions from any type are tracked
+      this._previousTypeCache.set(model, model.props.type);

       // Subscribe to props changes for each list block
       this._subscribeToModelChanges(model);

7-20: Doc comment is incomplete — mentions only deletion, but the extension also handles type changes.

The JSDoc (lines 7–20) describes only the deletion scenario, but the extension also renumbers when a list item's type changes from numbered to a non-numbered type. Consider updating the comment to reflect both cases.

Comment thread blocksuite/affine/blocks/list/src/list-numbering-watcher.ts
Comment thread blocksuite/affine/blocks/list/src/list-numbering-watcher.ts
Comment thread blocksuite/affine/blocks/list/src/list-numbering-watcher.ts Outdated
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 2, 2026

Codecov Report

❌ Patch coverage is 55.55556% with 48 lines in your changes missing coverage. Please review.
✅ Project coverage is 56.98%. Comparing base (35e1411) to head (81cfe4d).
⚠️ Report is 114 commits behind head on canary.

Files with missing lines Patch % Lines
...e/affine/blocks/list/src/list-numbering-watcher.ts 55.14% 46 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           canary   #14414      +/-   ##
==========================================
- Coverage   57.51%   56.98%   -0.53%     
==========================================
  Files        2859     2860       +1     
  Lines      154468   154576     +108     
  Branches    23336    23253      -83     
==========================================
- Hits        88845    88090     -755     
- Misses      62720    63493     +773     
- Partials     2903     2993      +90     
Flag Coverage Δ
server-test 75.11% <ø> (-0.99%) ⬇️
unittest 33.86% <19.44%> (-0.09%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

1 participant