Skip to content

fix(tables): rework lock settings as Table Security and gate locked actions - #7853

Merged
j15z merged 7 commits into
stagingfrom
fix/row-locking-columns-bug
Sep 16, 2026
Merged

j15z merged 7 commits into
stagingfrom
fix/row-locking-columns-bug

Conversation

@j15z

@j15z j15z commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

Replaces Lock settings with a Table Security modal, and makes a denied action explain itself where the user meets it instead of failing on save or doing nothing at all.

  • Table Security modal: Deny / Allow rows for Inserting Rows, Updating Rows, Deleting Rows, and Changing Table Schema. The rows map one-to-one onto the existing four lock flags — Deny is a set lock — so an unconfigured table opens on four Allows and every viewer sees the same state. No backend or migration change.
  • Changing Table Schema denied: New column keeps a lock icon, is aria-disabled, and explains itself in a tooltip. Edit column, Insert column left/right, and Delete column are all disabled with tooltips. Clicking a column header opens the config panel read-only — values stay readable and selectable, the controls are inert, and Save carries the reason. Hide column is untouched: hiding a workflow output is a metadata change no lock covers.
  • Updating Rows denied:
    • Double-click and Enter/F2 still open the cell editor, read-only, so values can be selected and copied. Text and number inputs are read-only, date cells hide the calendar, and select options are disabled.
    • The expanded text editor opens read-only with Save disabled and the reason on hover; its footer hint no longer advertises ↵ save.
    • Clicking a checkbox cell raises the same notice the keyboard paths already did, instead of silently doing nothing.
    • An empty cell that can't be edited opens nothing (also applies to users without edit access).
  • Inserting Rows denied: New row keeps a lock icon and tooltip; Insert row above/below and Duplicate row are disabled.
  • New rows through the Add Row form: a blank row doesn't work when updates are denied (typing into it is an update) or when any column is required (the server rejects an empty row). In either case New row, Shift+Enter, and Insert row above/below open the Add Row form at that position, and the form inserts the complete row in one request.
  • Row form:
    • Add mode with an optional insert position; Add Row and Update Row stay disabled until every required field has a value.
    • Values are read and written by column id. Before this, Edit Row showed empty fields and saved under the column name on columns that have an id.
    • Only touched fields are sent, and in edit mode only changed ones, so an untouched empty column is no longer written as null and a no-op save closes without a write. Checkboxes are the exception on insert: they always carry a concrete boolean.
    • A rejected write renders once, inline. Row mutations take an opt-in suppressErrorToast; the cache self-heal on a 423 still runs.
    • Date columns use one date-and-time picker, matching the grid.
  • Copy: notices speak the modal's Allow/Deny vocabulary and name the row that denies the action; tooltip strings live in lock-copy rather than at each call site.
  • ChipDatePicker: follows InsideModalContext like ChipDropdown (fix(ui): keep modal popovers interactive #7435 missed it), so its calendar is clickable inside modals, including the document tags modal. Single mode gains showTime and timeLabel.

Reviewer notes:

  • Rows added through the Add Row form aren't added to the grid's undo history.
  • A disabled Save doesn't say which required field is empty; required fields are marked with *.
  • A new TTL value picked in the Add Row form saves with a -00:00 offset while date columns use the viewer's local offset. Existing behavior, left as is.
  • Two pre-existing server bugs surfaced while testing and are deliberately out of scope: the metadata endpoint can strip a workflow group's dependencies on a schema-locked table, and unique values and row caps can be exceeded by parallel inserts. Tracked separately.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation
  • Other: ___________

Testing

  • Unit tests for:
    • Table Security modal: always renders the four rows, starts an unconfigured table on Allow, mirrors server locks with Deny as a set lock, saves denied actions as locks, and keeps the modal open with the draft intact when the save fails.
    • Locked New column trigger: stays focusable, shows its tooltip, and opens no menu.
    • Expanded cell editor: read-only with Save disabled and the reason on hover, footer hint drops ↵ save while blocked, and saves normally when allowed.
    • Inline editors: read-only text input, and a read-only date input without the calendar.
    • Row form: add mode inserts once under column ids and at a requested position, Save stays disabled until required fields have values, edit mode reads and saves by column id, a no-op edit writes nothing, an insert omits untouched columns but still sends checkboxes, and expiration date and time edits keep their offset.
  • type-check and lint pass; 607 tests across tables/[tableId] and hooks/queries pass.
  • Exercised in the running app against a local database:
    • Read-only column panel (typing leaves the value unchanged), tooltips on the disabled column-menu rows, the checkbox-cell notice, the reworded on-open notice, the modal without its switch, and a unique-violation error appearing only inside the form.
    • Server enforcement, called directly: every row, column, upsert, CSV import, and archive path returns 423 on a denied action and changes nothing; only admins can change locks (403 otherwise), and a write-only user sees no Table Security entry.
    • A lock set in another tab reaches an open grid within ~2s over SSE, and submitting an already-open Add Row form is then refused by the server with the reason shown inline.
    • Scale: a 105k-row table pages 1,000 rows in ~90ms, virtualizes to ~47 rendered rows while scrolling, and lock toggles take 14–19ms.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

Screenshots/Videos

Before:
Screenshot 2026-09-15 at 5 18 55 PM
Screenshot 2026-09-15 at 5 18 51 PM

After:
Screenshot 2026-09-15 at 5 50 32 PM
Screenshot 2026-09-15 at 5 50 24 PM

🤖 Generated with Claude Code

@vercel

vercel Bot commented Sep 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
docs Skipped Skipped Sep 15, 2026 11:50pm UTC

Request Review

@greptile-apps

greptile-apps Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

The PR appears safe to merge; the previous findings are resolved and no new actionable defects remain.

Summary

The PR replaces the previous lock-settings presentation with four authoritative Table Security permissions and consistently gates table actions according to those permissions.

  • Saves only lock rows explicitly changed by the current administrator, avoiding stale overwrites of untouched settings.
  • Adds read-only schema and cell-editing states with contextual lock explanations.
  • Routes inserts that require complete data through an add-row form with positional insertion and required-field validation.
  • Uses canonical column IDs and changed-only payloads for row mutations.
  • Adds modal-aware date-and-time picker behavior and focused regression coverage.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Authoritative table locks] --> B[Table Security modal]
  B --> C[Partial lock patch]
  C --> A
  A --> D[Table grid permissions]
  D --> E[Allowed inline action]
  D --> F[Read-only or disabled action]
  D --> G[Add Row form]
  G --> H[Complete positional insert]
Loading

Reviews (2) · Last reviewed commit: "fix(tables): stage only the Table Securi..."

…d tables

- Update-locked tables open cell editors read-only; the expanded editor disables Save with the reason on hover, and empty cells that can't be edited open nothing
- New row opens the Add Row form when only updates are locked
- Row form reads and writes values by column id and uses one date-and-time picker
- ChipDatePicker follows InsideModalContext so its calendar is clickable in modals, and gains showTime/timeLabel in single mode
… on required fields

- New row, Shift+Enter, and Insert row open the Add Row form at their position when updates are locked or any column is required
- Add Row and Update Row stay disabled until every required field has a value
- Add mode accepts an insert position; Shift+Enter anchors to the neighbor row id
j15z and others added 3 commits September 15, 2026 16:24
Clicking a column header opened the full column editor on a schema-locked
table: every field was editable and Save only failed once the server refused
it. The header click is the primary way into that panel, so it now opens
read-only — values stay readable and selectable, a disabled `<fieldset>` makes
the controls inert, and Save carries the lock reason. Clicking a checkbox cell
on an update-locked table did nothing at all, while the keyboard paths
explained themselves; it now raises the same notice.

The column menu disabled only "Edit column" while "Insert column left/right"
and "Delete column" stayed live and explained the lock after the click. All
four are disabled now, each with a tooltip. A disabled `DropdownMenuItem` sets
`pointer-events: none`, so the tooltip wraps the row rather than the item.
"Hide column" is untouched: hiding a workflow output is a metadata change no
lock covers.

Notices now speak the modal's Allow/Deny vocabulary and name the row that
denies the action, and the tooltip strings live in `lock-copy` instead of
being written out at each call site. Drops the "This table is append-only"
copy, which no path could reach once New row and Shift+Enter started opening
the add-row form.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…s once

The add/edit row form sent every column on every save. An untouched empty
column was written as `null`, so a no-op edit still bumped the row, and an
insert filled in nulls for columns the user never opened. It now sends only
the fields the user touched, and in edit mode only those whose value actually
differs — a save with nothing changed closes without a write. Checkboxes stay
the exception on insert: they always carry a concrete boolean, so a required
one the user never clicked still reaches the server as `false`.

A rejected write also arrived twice: the modal rendered the message inline and
the mutation toasted the same sentence. Row mutations take an opt-in
`suppressErrorToast` so the form owns its own failure; the cache self-heal on
a 423 still runs, only its toast is dropped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… store

"Enable Table Security" had no server representation: enabled-with-everything-
allowed and never-configured both save four `false` flags, so the difference
lived only in the browser that set it. Anyone else — another device, another
admin — saw the table as unconfigured, and the per-action choices behind the
switch were remembered per device too.

The modal now always shows the four Allow/Deny rows, mapped one-to-one onto
the server flags, so an unconfigured table opens on four `Allow`s and every
viewer sees the same state. That removes the reason for the preference store,
which is deleted along with its helpers and test. Stale
`table-security-preferences` keys are left where they are; nothing reads them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@j15z j15z changed the title fix(tables): block schema-locked column edits and rework lock settings as Table Security fix(tables): rework lock settings as Table Security and gate locked actions Sep 15, 2026
The modal reset its draft only when it opened, so a lock another admin changed
while it sat open went stale behind it: the controls kept rendering the old
values and Save submitted all four flags, overwriting the newer state.

Only the rows this admin moves are staged now. Every other row keeps rendering
the authoritative value, so a concurrent change shows up in the open modal
instead of hiding behind it, and Save sends just that patch — the route already
takes a partial — so an untouched row can't carry a stale flag over someone
else's change. A row both admins moved is the one real conflict, and there the
explicit choice wins.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@j15z

j15z commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator Author

@greptile

@j15z

j15z commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor

cursor Bot commented Sep 15, 2026

Copy link
Copy Markdown

Skipping Bugbot: Bugbot is disabled for this repository. Visit the Bugbot dashboard to update your settings.

@j15z
j15z merged commit f4845cc into staging Sep 16, 2026
35 checks passed
@j15z
j15z deleted the fix/row-locking-columns-bug branch September 16, 2026 00:18
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