Skip to content

fix: ensure unique item IDs when importing checklists (#501)#534

Merged
fccview merged 1 commit into
fccview:developfrom
reniko:fix/duplicate-item-ids-501
May 29, 2026
Merged

fix: ensure unique item IDs when importing checklists (#501)#534
fccview merged 1 commit into
fccview:developfrom
reniko:fix/duplicate-item-ids-501

Conversation

@reniko

@reniko reniko commented May 29, 2026

Copy link
Copy Markdown
Contributor

Problem

Fixes #501. When a checklist is imported via file-drop (without per-item
metadata), several items get identical IDs. Since checkbox toggling is keyed on
the item ID, checking one box toggles every item that shares that ID — e.g.
clicking "Item D1" also toggled Group A, Item A1, B1 and C1.

Root cause

Two markdown parsers assign item IDs:

  • app/_utils/checklist-utils.ts (parseMarkdown) generates
    ${id}-${level}-${counter} with a global counter → unique.
  • app/_utils/client-parser-utils.ts (parseChecklistContent
    buildNestedItems) generated ${id}-${currentItemIndex} with no level
    segment and reset the index to 0 for each nested group. Group headers and
    child items therefore shared the same number space and collided
    (<file>-0 appeared 5× in the repro).

The IDs that actually get persisted on file-drop come from the client parser,
so the earlier server-side change in #509 didn't cover this path.

Fix (client-parser-utils.ts)

  • Replace the per-group index fallback with a monotonic generateItemId(level)
    (${id}-${level}-${globalCounter++}), matching the scheme already used by the
    server parser. Guarantees uniqueness regardless of nesting.
  • IDs stored in inline item metadata are still preserved (backward compatible).
  • Add a post-parse dedupe pass: if duplicate IDs remain (e.g. from files
    imported before this fix), the first occurrence is kept and later duplicates
    get a fresh unique ID. Already-broken checklists self-heal on the next save.

Tests

Added tests/utils/client-parser-utils.test.ts:

yarn lint, tsc --noEmit and yarn test:run all pass.

The client-side parseChecklistContent generated fallback item IDs as
`${id}-${currentItemIndex}` and reset currentItemIndex to 0 in each
recursive call for nested children. Group headers and their nested
items therefore shared the same numeric range, producing duplicate IDs
across siblings and groups. Clicking one item toggled every other item
with the same ID.

The server-side parser (parseMarkdown in checklist-utils.ts) already
avoided this by using a monotonic counter combined with level
(`${id}-${level}-${counter}`). Align the client parser with the same
scheme so a file dropped into the importer is assigned the same IDs
that the server would produce.

Also add a post-parse dedup pass so already-imported files with broken
duplicate IDs heal themselves on the next save: the first occurrence
keeps its ID, every later collision is reassigned a fresh unique ID.
Stored metadata IDs are still honoured.

Tests cover a 4-group, 5-children-each fresh parse (all IDs unique),
preservation of stored item-metadata IDs, and dedup of pre-broken
imported content.
@fccview fccview merged commit 27c45d4 into fccview:develop May 29, 2026
2 checks passed
@fccview

fccview commented May 29, 2026

Copy link
Copy Markdown
Owner

Works! thank you for the assist ❤️

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.

2 participants