Skip to content

refactor(web): replace floating-form store bus with idiomatic floating-ui#1893

Merged
tyler-dane merged 2 commits into
mainfrom
claude/sad-burnell-2cd564
Jun 30, 2026
Merged

refactor(web): replace floating-form store bus with idiomatic floating-ui#1893
tyler-dane merged 2 commits into
mainfrom
claude/sad-burnell-2cd564

Conversation

@tyler-dane

Copy link
Copy Markdown
Contributor

Context

The Day view positioned its event form through a hand-rolled global state bus in useOpenAtCursor.tsfive createExternalStore singletons (open, nodeId, placement, strategy, reference) read via useSyncExternalStore and written imperatively from interaction handlers, plus a useFloatingAtCursor wrapper and a setTimeout(…, 10) / queueMicrotask to fight timing. All of it existed to feed one useFloating instance shared between the event form and the context menu (discriminated by a CursorItem enum).

Meanwhile the rest of the app — including the Week view (useEventForm) — already uses floating-ui the intended way: a local useFloating per component with the reference attached via refs.setReference or a virtual element at the cursor. The Day view was the lone outlier.

This PR converges both views onto the existing useEventForm hook and deletes the store bus.

What changed

  • Open state is derived from Redux. A new status.isFormOpen flag on the draft slice (kept distinct from isDrafting/activity — drag-to-create makes a gridClick draft without opening the form) plus a selectIsEventFormOpen selector. Replaces openStore + nodeIdStore.
  • The form anchors to the rendered draft element via refs.setReference, merged into the active draft card with useMergeRefs — exactly like the Week view's GridDraft. Removes the registry lookups, queueMicrotask, and setTimeout. Replaces referenceStore.
  • useEventForm is the shared hook for both views, extended with optional floating/dismiss overrides. The Day view supplies its own positioning middleware (relocated to dayEventFormFloating.ts); the Week path is unchanged.
  • The Day context menu gets its own local useFloating + virtual element at the cursor (matching GridContextMenuWrapper), removing the shared instance, the CursorItem discriminator, and the hidden anchor div.
  • Deletes useOpenAtCursor.ts and useFloatingAtCursor.ts. The shared external-store.util is kept (other consumers remain). Net −350 lines.

Verification

  • bun run type-check and biome lint: clean.
  • bun test (events ducks, Day, Forms, Week views): 286 pass, 0 fail, including the updated DayInteractionCoordinator and useCloseEventForm suites.
  • Manual, in the running app (temporary account → Day view):
    • Click an event → form opens anchored to it; outside-click dismisses and discards.
    • Right-click an event → context menu opens at the cursor, independent of the form.
    • Context-menu Edit → form opens.
    • No console errors across the flows.

🤖 Generated with Claude Code

…g-ui

The Day view positioned its event form through a hand-rolled global state bus
(`useOpenAtCursor`): five `createExternalStore` singletons plus a
`useFloatingAtCursor` wrapper and `setTimeout`/`queueMicrotask` timing hacks,
feeding one `useFloating` instance shared between the form and the context menu.

Converge both views onto the existing, view-agnostic `useEventForm` hook:

- Form "open" is derived from Redux: a new `status.isFormOpen` flag on the draft
  slice (kept distinct from `isDrafting` so drag-to-create stays closed) plus a
  `selectIsEventFormOpen` selector. Replaces `openStore`/`nodeIdStore`.
- The form anchors to the rendered draft element via `refs.setReference` (merged
  into the active draft card), mirroring the Week view. Removes the registry
  lookups, `queueMicrotask`, and `setTimeout`. Replaces `referenceStore`.
- The grid (Day + Week) positioning middleware now lives inside `useEventForm`'s
  shared timed/all-day branch — no per-view floating config. Callers only pass a
  `dismiss` override when their dismiss behavior differs (the Day all-day press
  guard).
- The Day context menu gets its own local `useFloating` + virtual element at the
  cursor (matching `GridContextMenuWrapper`), removing the shared instance, the
  `CursorItem` discriminator, and the hidden anchor div.

Deletes `useOpenAtCursor.ts` and `useFloatingAtCursor.ts` (the shared
`external-store.util` is kept — it has other consumers).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@tyler-dane tyler-dane force-pushed the claude/sad-burnell-2cd564 branch from c6e05e5 to 19dd047 Compare June 30, 2026 03:32
@tyler-dane tyler-dane marked this pull request as ready for review June 30, 2026 04:32
@tyler-dane tyler-dane merged commit 24249e0 into main Jun 30, 2026
9 checks passed
@tyler-dane tyler-dane deleted the claude/sad-burnell-2cd564 branch June 30, 2026 04:32
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