0.5.2 — One settings-modal chrome across Create/Edit View + Edit Table, persistent select options, notification-freeze fix
Create View, Edit View, and Edit Table now render on the same shared `ModalShell` — centered, solid `bg-background` panel with tinted config rail, header subtitle, pinned footer, and popover-aware Escape. Creating a select option from a cell now persists to the column, so filter pickers everywhere see it. And the grid livelock that froze the app after clicking a record-created notification is fixed.
What's new
One centered-modal chrome for Create View / Edit View / Edit Table
CreateViewModal's chrome — dimmed blurred backdrop, solid
bg-background panel at rounded-2xl + sheet elevation, title /
subtitle header with close button, pinned footer bar, and
popover-aware Escape — is now a shared ModalShell component. Both
sibling settings surfaces move onto it, and the deprecated
right-side SideSheet chrome is deleted.
- Edit View settings (
ViewSettingsSheet) is now a centered modal instead of a right-side slide-in, matching Create View's tintedbg-sidebar/50config-rail styling,SectionHeadingrhythm fromModalFormPrimitives, and Cancel + primary Save footer (errors surface in the footer). The gear on any saved-view row — sidebar tree and the grid header's view dropdown — opens it. - Edit Table settings (
EditTableSheet) also moves onto the shared shell: the table's name and field count in the header subtitle, a Fields and Settings tab bar pinned above the scrolling body, and the same Cancel + Save footer. All tab content is unchanged — the switch is chrome-only. CreateViewModalis refactored onto the shell too; its chrome-contract tests pass unchanged. The rail width is 400 px (up from 320 px in the first drop), so filter-value dropdowns show the picked option's full label instead of collapsing to a ~36 px sliver.- Popover-aware Escape. With an open value dropdown or
date-picker layer (
data-popover-layer), Escape closes only that layer; the modal closes only when no popover is open. A mis-typed refinement never drops the whole draft.
See Data → Create view modal, Data → Saved views, and Data → Per-table settings.
Select options created from a cell now persist to the column
Creating an option via a select cell's Create ⟨value⟩ action
used to only write the value onto that row — never appending it to
the column's stored options list. That produced "ghost" options
that vanished from pickers when the last row holding them was
edited, and that never appeared in surfaces reading only stored
options — notably the Create View modal's filter-value dropdown
(e.g. CABoom on prospects.source was missing there while
visible in the cell editor).
- Persist on create. A new shared
addSelectOptionhelper insrc/lib/columnOptions.jsappends the created value to the column's stored options; no-ops for duplicates, blanks, non-select columns, and value-set-backed columns (whose options live in the value-set store). Wired into every create surface: the grid's cell editor popup (select + multi-select),CanvasDataCard, andDataPage. - Filter pickers see stored options ∪ row values. The
enrichment the grid's filter popover already did is extracted
into a shared
enrichSelectFieldOptionshelper, and now also used by the Create View modal and the Edit View settings modal — so any ghost values still living in existing rows remain filterable without a data migration.ViewSettingsSheetfetches the table's rows on open, since it can be opened from the sidebar before the grid has loaded them. - Cell editors in
CanvasDataCard/DataPagealso merge row values into their option lists, matching the grid.
Fixes
Grid livelock that froze the app after notification clicks
Clicking a record-created notification opened the record, then the entire page stopped responding to clicks until a hard refresh. Root cause was on the Data page from the moment it mounted: 'Maximum update depth exceeded' fired continuously with zero interaction —
LargeWidgetrebuiltvisibleColsas a fresh array literal on every render, soorderedCols → toolbarFields → viewCountsall changed identity every render.- The
onViewStateChangeeffect (keyed onviewCounts) re-fired after every commit, and itssetGridViewStateinDataWorkspacere-rendered the grid → infinite passive-effect loop. - React Router v7 runs navigations as
startTransitions; the endless normal-priority updates kept interrupting them, so navigations never committed — the URL changed but the UI didn't, and every later click died silently. The notification click was just the first navigation users hit from that page.
The fix stabilizes identities down the chain: visibleCols /
userCols / systemTrailing are memoized in LargeWidget;
useViewCounts returns the previous object when counts are
value-equal; and DataWorkspace bails out of gridViewState
updates when nothing changed — a circuit breaker so future
identity churn can't re-arm the loop.
Deep-link resilience for record notifications
Same fault line, addressed under the hood:
RecordDeepLinkHostandCalendarDeepLinkHostno longer callsetSearchParamsduring render (illegal render-phase navigation in Router v7). Dead links are cleared from effects only after the lookup has settled.RecordDeepLinkHostrefetches the target table before declaring a row missing, so a notification pointing at a row newer than the local cache — the teammate-notification race — opens the sheet instead of being silently discarded.fetchRowsin-flight dedup now returns the shared request promise instead of[], so concurrent callers all wait on the same result.
Under the hood
src/components/shared/ModalShell.jsx(new) — the shared centered-modal chrome.CreateViewModal,ViewSettingsSheet, andEditTableSheetall mount on it. The now-orphanedsrc/components/shared/SideSheet.jsxis deleted;ModalShellis documented under §Modals inDESIGN-PHILOSOPHY.md.src/lib/columnOptions.js(new) —addSelectOption(persist on create) andenrichSelectFieldOptions(stored options ∪ row values for filter pickers), with 13 unit tests.ViewSettingsSheetopens the row cache — fetches rows on mount so the enriched value picker has row values to work with when the sheet is opened from the sidebar before the grid has loaded.useViewCountsidentity-stable — returns the previous object when counts are value-equal; combined with memoizedvisibleCols/userCols/systemTrailing, breaks the render loop at its source.
What's next
- Bring the medium canvas
DataTableViewerWidget's edit-view surface onto the same modal shell as its full-app siblings so every view / table settings entry point renders the same panel. - Extend the shared
ModalShellto the next batch of settings surfaces (NewTableModal,CreateEventSheet) so the whole in-app settings-modal family reads as one system.
See Data → Saved views, Data → Create view modal, Data → Per-table settings, and Data → Toast notifications on record creation.
0.6.0 — SMS becomes a native app on live PitchPrfct messaging, dial email tab searches all mail
SMS graduates from a hidden three-panel canvas widget to a first-class native app — full-height inbox rail with search + All/Unread/Starred tabs, thread pane, service picker, and real-time messages via a HMAC-verified PitchPrfct webhook → Supabase Realtime pipeline. Dial's email tab now searches all mail (not just inbox), incoming email no longer auto-marks itself read on arrival, and dialer SMS sends reuse the conversation's existing number instead of thrashing the phone-numbers API.
0.5.1 — Create view is a modal with a live record preview
Creating a saved view now opens a proper modal with name, sharing, filters, and sort in one place, plus a live record preview that shows exactly which rows the view will match — before it exists.