Skip to content

anchoredPopover.close() can clobber a newer popover sharing the same dom refKey #603

Description

@BorisTyshkevich

anchoredPopover's close() (now in src/ui/popover.ts, createAnchoredPopovers) removes whatever node currently occupies app.dom[refKey] without verifying that node is the one it opened:

close() {
  if (getRef(refKey)) { ... /* removes it unconditionally */ }
}

Repro: open popover A on refKey, open popover B on the same refKey (A's slot is now B), then invoke A's retained close() handle — B gets removed/clobbered even though A never touched it, and the keyboard owner it releases may no longer belong to A either (keyboard release itself is idempotent via the released flag in acquireKeyboardOwner; the DOM removal is not).

This predates the #588 phase-4 composition-root refactor (the code moved verbatim from src/ui/app.ts, byte-identical) — surfaced during that refactor's readiness review, not introduced by it. Deliberately not fixed there, per this repo's "surface out-of-scope findings, don't bury them" discipline: fixing it would have been a behavior change inside a PR whose scope and review budget were both set for a pure structural extraction.

A regression test pinning the current (buggy) behavior lives in tests/unit/popover.test.ts (search for "I-21" / "stale-clobber").

Suggested fix direction: give each open() call a token/generation and have close() no-op if the ref slot's current occupant token doesn't match the token the closer was handed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    inboxFiled mid-task; not yet triaged into the roadmap

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions