Skip to content

Replace positional diff with CollectionDifference-based shape matching#484

Open
wojo wants to merge 1 commit intoswiftbar:mainfrom
wojo:fix/collection-difference-diff
Open

Replace positional diff with CollectionDifference-based shape matching#484
wojo wants to merge 1 commit intoswiftbar:mainfrom
wojo:fix/collection-difference-diff

Conversation

@wojo
Copy link
Copy Markdown
Contributor

@wojo wojo commented Mar 25, 2026

Summary

Alternative fix for #482 — replaces the naive positional diff with Swift's CollectionDifference using shape-based identity matching. This correctly handles mid-list inserts and removes without misaligning item properties.

See #483 for the simpler fallback approach (Option 1).

How it works

Each MenuItemNode gets a ShapeFingerprint that captures its structural role:

  • isSeparator — separators match other separators
  • hasImage — image-only lines are distinct from text
  • fontName — monospace lines (e.g. font=Menlo) are distinct
  • titleKey — stable prefix up to first : or first word ("Battery:", "Climate:", "Home")
  • hasChildren / hasFold — structural distinctions

An occurrence counter disambiguates duplicates (e.g. multiple separators). CollectionDifference.inferringMoves() uses these tagged nodes to correctly track items across position shifts.

Pros / Cons vs Option 1 (#483)

Option 1: Full rebuild on structural change (#483)

Pros:

  • Minimal code change (~15 lines)
  • Zero risk of new edge cases
  • Easy to review and reason about

Cons:

Option 2: CollectionDifference shape matching (this PR)

Pros:

Cons:

  • More code (~150 lines added)
  • Shape fingerprint heuristics may need tuning for edge cases (e.g. two items with identical shapes)
  • Falls back to remove+insert for true reordering (rare for plugins)

Files changed

  • MenuItemNode.swift — Added ShapeFingerprint struct, shapeFingerprint computed property, extractTitleKey() helper
  • MenuDiff.swift — Replaced diffMenuNodes() with CollectionDifference-based implementation, added TaggedNode wrapper

Test plan

The positional diff in diffMenuNodes() compares items by index. When
items are inserted or removed mid-list, all subsequent items shift and
get patched with wrong content — e.g. an image item inherits adjacent
text as its attributedTitle, rendering ghost text beside the image.

Replace with Swift's CollectionDifference using shape-based identity
matching. Each MenuItemNode gets a ShapeFingerprint (separator, has
image, font, title prefix, has children, fold) that captures its
structural role independent of volatile content. An occurrence counter
disambiguates duplicates. CollectionDifference.inferringMoves()
correctly tracks items across position shifts.

This preserves the incremental update benefit for all cases — items
that shift position are patched in-place rather than rebuilt, keeping
fold states, webviews, and menu interactivity intact.

Fixes swiftbar#482
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