[PR #688] [MERGED] fix(buffer): prevent WrongGeneration panic when set() replaces grapheme with same ID #1493

Closed
opened 2026-03-14 09:39:48 +03:00 by kerem · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/anomalyco/opentui/pull/688
Author: @remorses
Created: 2/14/2026
Status: Merged
Merged: 2/15/2026
Merged by: @simonklee

Base: mainHead: fix/grapheme-pool-wrong-generation


📝 Commits (4)

  • 5e57b93 fix(buffer): prevent WrongGeneration panic when set() replaces grapheme with same ID
  • b4df742 fix(buffer): use counted grapheme tracking to prevent WrongGeneration panics
  • cae1a9f Merge remote-tracking branch 'origin/main' into fix/grapheme-pool-wrong-generation
  • 167cb38 prettier:write

📊 Changes

6 files changed (+249 additions, -113 deletions)

View changed files

📝 packages/core/src/buffer.test.ts (+35 -0)
📝 packages/core/src/zig/buffer.zig (+14 -3)
📝 packages/core/src/zig/grapheme.zig (+34 -6)
📝 packages/core/src/zig/tests/buffer_test.zig (+110 -0)
📝 packages/core/src/zig/tests/grapheme_test.zig (+6 -0)
📝 packages/solid/tests/scrollbox-cleanchildren.test.tsx (+50 -104)

📄 Description

Fixes the GraphemePool WrongGeneration panic that crashes termcast e2e tests.

Root cause: OptimizedBuffer.set() removes the old grapheme from the tracker (decref to 0 → slot freed) before adding the new one. When old and new cells share the same grapheme ID but different packed char values (different extent bits), the slot goes to the free list between remove and add. Any intervening pool.alloc() can reuse it, bumping the generation counter, causing WrongGeneration on the subsequent incref.

Fix: when the new cell uses the same grapheme ID as the old one, pre-add it to the tracker before removing the old entry. This keeps refcount >= 1, preventing the slot from reaching the free list.

Before:  remove(X) → refcount=0 → FREED → alloc reuses → add(X) → PANIC
After:   add(X) → refcount=2 → remove(X) → refcount=1 → add(X) → no-op

Adds Zig + TypeScript regression tests exercising grapheme pool churn across render frames with dialog open/close cycles.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/anomalyco/opentui/pull/688 **Author:** [@remorses](https://github.com/remorses) **Created:** 2/14/2026 **Status:** ✅ Merged **Merged:** 2/15/2026 **Merged by:** [@simonklee](https://github.com/simonklee) **Base:** `main` ← **Head:** `fix/grapheme-pool-wrong-generation` --- ### 📝 Commits (4) - [`5e57b93`](https://github.com/anomalyco/opentui/commit/5e57b93a8765c6ee2b2f39dff47ce624db20a8a0) fix(buffer): prevent WrongGeneration panic when set() replaces grapheme with same ID - [`b4df742`](https://github.com/anomalyco/opentui/commit/b4df74271dbfd4645ef072b29960de5b4fa6f591) fix(buffer): use counted grapheme tracking to prevent WrongGeneration panics - [`cae1a9f`](https://github.com/anomalyco/opentui/commit/cae1a9fba32b027ecd6ac6e548dc646966a3056d) Merge remote-tracking branch 'origin/main' into fix/grapheme-pool-wrong-generation - [`167cb38`](https://github.com/anomalyco/opentui/commit/167cb3873e844e558189f5e5b68c65b51b0c078d) prettier:write ### 📊 Changes **6 files changed** (+249 additions, -113 deletions) <details> <summary>View changed files</summary> 📝 `packages/core/src/buffer.test.ts` (+35 -0) 📝 `packages/core/src/zig/buffer.zig` (+14 -3) 📝 `packages/core/src/zig/grapheme.zig` (+34 -6) 📝 `packages/core/src/zig/tests/buffer_test.zig` (+110 -0) 📝 `packages/core/src/zig/tests/grapheme_test.zig` (+6 -0) 📝 `packages/solid/tests/scrollbox-cleanchildren.test.tsx` (+50 -104) </details> ### 📄 Description Fixes the GraphemePool WrongGeneration panic that crashes termcast e2e tests. **Root cause**: `OptimizedBuffer.set()` removes the old grapheme from the tracker (decref to 0 → slot freed) before adding the new one. When old and new cells share the same grapheme ID but different packed char values (different extent bits), the slot goes to the free list between remove and add. Any intervening `pool.alloc()` can reuse it, bumping the generation counter, causing `WrongGeneration` on the subsequent incref. **Fix**: when the new cell uses the same grapheme ID as the old one, pre-add it to the tracker before removing the old entry. This keeps refcount >= 1, preventing the slot from reaching the free list. ``` Before: remove(X) → refcount=0 → FREED → alloc reuses → add(X) → PANIC After: add(X) → refcount=2 → remove(X) → refcount=1 → add(X) → no-op ``` Adds Zig + TypeScript regression tests exercising grapheme pool churn across render frames with dialog open/close cycles. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
kerem 2026-03-14 09:39:48 +03:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/opentui#1493
No description provided.