- secrets: revealed value gets a copy-to-clipboard with feedback,
auto re-hides after 30s, re-hides on window blur, and the timer is
cleared on unmount (no change to revealSecret or RLS)
- permissions matrix: a view-only notice when the viewer is not admin,
and a per-checkbox aria-label so the grid is usable non-visually
- OpsAclEditor: pills are now aria-pressed toggles with a visible
check when selected and a success toast on save
No server action, RLS, or permission logic changed.
- S6: unlayered :focus-visible ring for form controls and links so
keyboard focus is perceivable (was focus:outline-none + 1px border)
- S5: >=44px touch targets at the named hotspots (dashboard actions,
list-row buttons, ops pills/icons, customize number inputs); dense
rows use a 44px hit area without growing row height
- S8: 28 form fields associated via id/htmlFor; search inputs get
aria-label
- S7: claim/complete/delete/link handlers now surface result.error via
toast and stop instead of failing silently
Migration 023 (folded into schema.sql section 15). ADDITIVE and
backward compatible: with no permission/ACL rows the app behaves
exactly as before.
Schema:
- space_role_permissions (space_id, subject, permission); ops_acl
(space_id, entity_type secret|kb|process|area_lead, entity_id,
role). Full RLS, writes admin/board.
- user_effective_roles(uid,sid): built-in role + custom-role slugs +
area_lead:<id> for areas the member leads. SECURITY DEFINER.
- user_has_permission(uid,sid,perm): admin implicit-all else a grant
via an effective role. SECURITY DEFINER.
- secrets/knowledge_base SELECT rewritten as
`exact existing rule OR ops_acl match` (only widens; zero change
with no ACL rows). Seed + backfill default role permissions.
Area-lead roles reuse the existing area_leads table rather than
duplicating the concept: a row is a role, lead_id NULL = Vacant,
assigning sets lead_id, the member then holds area_lead:<id>.
App:
- lib/permissions-catalog.ts (codes + per-role defaults + guardrails),
Zod schemas, lib/actions/permissions.ts (setRolePermissions,
setOpsAcl, createAreaLeadRole, assign/unassign/delete).
- Customize gains Permissions (role x permission matrix, admin shown
implicit-all/locked) and Area leads (create/Vacant/assign) panels,
modular under customize/panels/. Reusable
components/ops/ops-acl-editor.tsx wired into the Secrets list.
Audit-found latent fixes:
- revealSecret errors on encrypted-but-no-ciphertext corruption
instead of silently returning the stale plaintext column.
- upsertAreaLead rejects null/empty area_code (NULLs are distinct in
unique constraints; allowed duplicate rows).
types/database.ts: added the two new tables. 271/271 tests pass,
build clean. KB-modal ACL wiring + members-row shortcut deferred and
tracked in HANDOFF.