Skip to content

feat(data): split system/custom table capabilities and lock system tables#71

Merged
DavidBabinec merged 3 commits into
mainfrom
feat/data-view-system-table-visibility
Jun 17, 2026
Merged

feat(data): split system/custom table capabilities and lock system tables#71
DavidBabinec merged 3 commits into
mainfrom
feat/data-view-system-table-visibility

Conversation

@DavidBabinec

Copy link
Copy Markdown
Contributor

What & why

The Data view treated all data_tables identically: anyone with data.tables.read saw the four internal system tables (posts/pages/components/layouts) alongside custom tables, updateDataTable had no system guard (only delete was blocked), and built-in field values were hand-editable in the grid. This splits the capability and locks system tables down — so e.g. a Client persona sees custom tables but never the system ones.

Capability split

Replaces data.tables.read/data.tables.manage with a system-vs-custom family:

Capability Grants
data.custom.tables.read See/browse custom tables
data.custom.tables.manage Create/rename/delete custom tables + fields
data.system.tables.read See/open the 4 system tables
data.system.tables.manage Add/manage custom fields + set primary field on a system table (identity + built-ins stay frozen)

Roles: owner/admin get all four; client gets data.custom.tables.read only. Resynced in both dialect migrations + SYSTEM_ROLES.

Server enforcement (UI mirrors it, never the sole gate)

  • List filtering (GET /data/tables): system tables returned only to system-read holders; content-row callers (loop/template pickers) still see the full list. Single-table reads gate the same way.
  • Granular table guard (assertSystemTableUpdateAllowed): on a system table, identity (name/slug/route/labels/kind) and built-in fields are immutable for everyone; custom fields + primary field are mutable (gated by data.system.tables.manage).
  • Row-write guard (lockedBuiltInCellKey): built-in cell writes rejected on the structural system tables (pages/components/layouts); posts built-ins and all custom fields stay writable. The site editor writes those trees via its own endpoints, so this never blocks it.

Client

  • TableSettings: reduced panel for system tables — only Display (primary field) + Fields (add/manage custom fields); General/Routing/Kind/Danger hidden, with a "system-managed" note.
  • fieldGuards: built-in fields on system tables fully locked (no edit/delete/reorder); custom fields + "Add field" stay available.
  • Grid/inspector cells render built-in values read-only on structural system tables.

Shared rules live in @core/data/systemTableGuard so server and client agree.

Tests

  • Unit: systemTableGuard.test.ts (frozen identity, built-in freeze, custom-field/primary-field allowed, value-lock predicate).
  • Integration (capability harness): dataSystemTableAccess.test.ts — custom-only persona sees no system tables; owner sees them; system-table rename + built-in removal rejected; custom-field add allowed; system-read-only persona denied management.
  • Updated existing capability/access/AI-gate tests for the renamed caps.

Verification

  • bun run build (tsc + vite) — pass
  • bun test — 5458 pass, 0 fail
  • bun run lint — clean

Notes: branched from main (not stacked on the layouts PR). The tree also carries an unrelated parallel session's AI/loops work-in-progress; only data-view files are staged here. Follow-up to the layouts single-source-of-truth PR.

🤖 Generated with Claude Code

DavidBabinec and others added 3 commits June 17, 2026 13:44
…bles

Split the single `data.tables.read`/`manage` pair into a system-vs-custom
family so a persona (e.g. the seeded Client role) can see and manage custom
tables without ever seeing the four internal system tables:

  data.custom.tables.read / data.custom.tables.manage
  data.system.tables.read / data.system.tables.manage

Server enforcement (the UI mirrors it, never the sole gate):
- GET /data/tables filters per family — system tables are returned only to
  holders of a system read cap; content-row callers (loop pickers) still see
  the full list. Single-table reads gate the same way.
- PATCH /data/tables/:id is kind-aware (canManageTable) and runs
  assertSystemTableUpdateAllowed: a system table's identity (name, slug, route
  base, labels, kind) and built-in fields are frozen for EVERYONE, while custom
  fields and the primary field stay mutable.
- Row writes reject built-in cell edits on the structural system tables
  (pages/components/layouts) via lockedBuiltInCellKey; posts built-ins and all
  custom fields stay editable. The editor writes those trees via its own
  endpoints, so this never blocks it.

Client:
- TableSettings renders a reduced panel for system tables (Display + Fields
  only; General/Routing/Kind/Danger hidden) with a "system-managed" note.
- fieldGuards locks built-in fields on system tables (no edit/delete/reorder)
  while custom fields and "Add field" stay available.
- Grid/inspector cells render built-in values read-only on structural system
  tables via isBuiltInValueLocked.

Shared rules live in @core/data/systemTableGuard so server and client agree.
Roles resynced in both dialect migrations + SYSTEM_ROLES (owner/admin get all
four; client gets data.custom.tables.read only). Docs + tests updated; new
unit + capability-harness integration coverage added.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- DataSidebar splits tables into "System" and "Your tables" groups (labels show
  only when both groups are present, so a custom-only persona sees a plain list).
- Remove the explanatory banner in TableSettings for system tables — the reduced
  panel speaks for itself.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Match the Site explorer's header pattern:
- Export / Import become icon buttons in the "Data tables" panel header
  (`headerActions`) instead of full-width footer buttons.
- "New table" becomes an icon button in the "Custom tables" section header.
- Section headers (System / Custom tables) carry a count, styled like the
  Site explorer sections. The Custom section always renders (empty → "None
  yet") so the New-table action has a home; the System section renders only
  when the user can see system tables.
- Rename "Your tables" → "Custom tables". Footer removed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@DavidBabinec DavidBabinec marked this pull request as ready for review June 17, 2026 12:34
@DavidBabinec DavidBabinec merged commit 65bdfb8 into main Jun 17, 2026
6 checks passed
@DavidBabinec DavidBabinec deleted the feat/data-view-system-table-visibility branch June 30, 2026 23:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant