Repository navigation
fix(console): five write affordances that read no grant now read the affordance-to-grant map, and a write census holds every write call site to a row (objectui#12082) - #12094
Conversation
…p's four grant shapes (objectui#12082) The object-view create button, the calendar quick-create and drag-to-reschedule, the kanban card move and the line-items panel's add / remove, each measured through the real MePermissionsProvider over create-only, edit-only, read-only and full, plus fail-open and the effective operation set. Claude-Session: https://claude.ai/code/session_01B1gHb9baeX7oioD5sHVm7z Co-authored-by: Claude <noreply@anthropic.com>
…affordance-to-grant map, and a write census holds every write call site to a row (objectui#12082) - object-view's New reads listNew; the calendar's quick-create reads the new calendarQuickCreate row and drag-to-reschedule calendarReschedule (a closed row withholds the handler CalendarView draws the affordance from); the kanban card move reads the new kanbanCardMove row (cards not movable, no mover handed to the board); a line-items panel's add / remove read relatedNew / relatedRowDelete on the child. - The enumeration pin gains a write census: every create / update / delete call site on a data source (or a write helper wrapping one) is keyed by file, enclosing symbols and verb, and has an entry naming the map row it sits behind and the file reading it, or why it is not a row's, or that it is an unmapped write affordance (the backlog the census found). Claude-Session: https://claude.ai/code/session_01B1gHb9baeX7oioD5sHVm7z Co-authored-by: Claude <noreply@anthropic.com>
…gin docs; changeset; the census reads its helper table by own key (objectui#12082) Claude-Session: https://claude.ai/code/session_01B1gHb9baeX7oioD5sHVm7z Co-authored-by: Claude <noreply@anthropic.com>
…ite-affordance-census
…ctui#12078 landed on main (objectui#12082) The merge of main brought the chatter's sys_comment_reaction create / delete into RecordDetailView's reaction toggle; both are entered as unmapped write affordances beside the legacy sys_comment update they sit next to. Claude-Session: https://claude.ai/code/session_01B1gHb9baeX7oioD5sHVm7z Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
|
Contract reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: ① Derived judgmentsThe hop from the reviewed head — a base merge, no change to the diff. The merged Does this PR's census or any pin read #12100's region — no. The write census walks the file for Carried forward unchanged from
② Semver levelUnchanged from
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Part of #12082 (the five affordances and the write census; the census's 21 unmapped write sites stay on the card as its remainder, per the seat's answer C)
Clause-②: yes
The card's remainder, as triage scoped it in comment 6095316139: five write affordances that read no grant at all now read the affordance-to-grant map, and the map's enumeration pin gains a census of WRITE call sites, so an affordance that reads no grant can no longer hide from it. Dispatched by the
domain:uiseat 3 claim 6095684352, sessionhttps://claude.ai/code/session_01B1gHb9baeX7oioD5sHVm7z.What changed
object-viewtoolbar New (@object-ui/plugin-viewObjectView,renderToolbar)listNew(reused: it is an object list's New)handleDateClickDefault) and a week / day range drag (handleTimeRangeSelectDefault)calendarQuickCreate(new: create)onDateClick/onTimeRangeSelectnot handed toCalendarView, which draws the affordance from handler presence: no dialog, no range draghandleEventDropDefault)calendarReschedule(new: update)onEventDropnot handed: events not draggablehandleCardMove/persistCardMove)kanbanCardMove(new: update)aria-disabled), and the board gets no moverrecord:line_itemsadd a line (Add, entry row, Duplicate) / remove a linerelatedNew/relatedRowDeleteon the CHILD (reused: the same two writes a related list makes)allowAdd/allowDeletefalseresolveAffordance(object policy, effective API operation set, caller's grant). With no permission provider mounted every one reads open, as the map defines (fail-openunchanged).onDateClick/onEventDroponObjectCalendaris the host's channel and is handed through unchanged.cardsMovableonKanbanBoardCoreand the lazy board (read through a context by every card); never a schema key; defaulttrue, soKanbanRendererhosts are unchanged.handleTimeRangeSelectDefaultinObjectCalendar.tsx, wired toCalendarView'sonTimeRangeSelect. Assumption 3 held: the line rows' semantics matchrelatedNew/relatedRowDelete, so no new rows there.The write census (closing the limit, not stating it)
In
packages/plugin-form/src/affordanceGrantMap-12082.test.tsx, beside the existing grant-read census:packages/*/src,apps/*/src, tests excluded), parsed with the TypeScript parser. A write call site is a member call naming aDataSourcewrite (create,update,delete,bulk,bulkUpdate,bulkDelete,batchTransaction) with at least the interface's arity, which separatesds.delete(o, id)from aMap/Setdelete(key), or a call of a write helper (runBatchTransaction,emulateBatchTransaction,recordDelete.run,saveWithOcc).FILE :: enclosing symbols :: verb(symbol-keyed, never a line address).WRITE_SITES: every site has an entry of one kind:mapped(rows, and the files that read them),helper,door(a DataSource implementation or engine),outOfFamily(with the reason) orunmapped(a write affordance with no row yet, named one by one).mappedentry names map rows of its own verb, each read by a file it names; the helper table and the helper entries name each other; a recognizer control on a synthetic source (including a collectiondelete(key)and prototype-named calls that must not count).d18c611d2: 93 sites. 33 mapped, 24 door, 6 helper, 9 out of family, 21 unmapped. (The instrument is the pin; these counts are a reading at that commit.)Measured on main first
The four runtime pins were run against the BASE source (
023f00d4, the seven source files checked out from it under a restore trap, markers confirmed absent, restore proven:git diff HEADempty and all seven blobs equal HEAD): 15 failed, 15 passed. Every red is the defect direction (the affordance offered to a caller whose grant denies it:expected true to be false,onDateClick: expected [Function] to be undefined,aria-disabled 'false'where'true'was due, the New button present); every allowed shape and fail-open leg was green on main too.Tests (at
d18c611d2unless noted)ObjectView.createGrant-12082(plugin-view),ObjectCalendar.writeGrant-12082,ObjectKanban.moveGrant-12082,LineItemsPanel.lineGrant-12082, plus the census inaffordanceGrantMap-12082: 5 files, 63 passed.rowCrudAffordances.test.ts(200 files, 3995 passed, 27 skipped); plugin-view 75 files (698 passed) and plugin-kanban + plugin-calendar 128 files (911 passed, 95 skipped), both at02cc3c82f(the merge of main since then touched only app-shell and this branch's census ledger).type-check(with the hyphen; each echoed its script name, 0error TS): core, plugin-kanban, plugin-calendar, plugin-form, plugin-view at02cc3c82f.ablation-replace.mjs(WRAP mode, predictions written first, every restore proven blob == HEAD andgit diff HEADempty), onObjectKanban.tsx:rowEditinstead ofkanbanCardMove(same verb, so runtime identical): kanban pin green, census 1 failed, namingpersistCardMove: no file it names reads kanbanCardMove. As predicted.dataSource.deletein a new handler: census 1 failed,unlistednamingObjectKanban > archiveCard :: delete. As predicted. The first A3 attempt was a no-op (its replacement re-contained the anchor, so the tool refused it with anchor 1 to 1 and restored); it was redone with a replacement that does not contain the anchor.check:control-bytes,check:new-line-citations(0 new),check-changeset-presence/-claims/-no-major/-overwrite,check:pending-changeset-literals,check:vi-mock-specifiers/-inherit/-override-shape,check:test-path-roots,check-hand-rolled-comment-maskatd18c611d2;check:doc-links,check:doc-fences,check:phantom-deps,check:self-import,check:esm-specifiers,check:unreferenced-sources,check:handler-key-reads,check-type-check-coverage,check-lint-coverage,check-changeset-fixedat02cc3c82f: all exit 0.no-explicit-anywarnings in the new test files, the sibling tests' fixture idiom.check:eager-closure(needs a base and a head console build; the eager-closure delta of three const rows in@object-ui/coreis left to the Bundle Analysis workflow);check:readme-exports(the run's population collapsed with 21 packages unbuilt in this worktree, a prerequisite, not a verdict; the core README edit is prose with no import binding); the fullscripts/__tests__suite (CI).Acceptance notes
The census found a backlog beyond the five. 21
unmappedsites, each named in the ledger with its reason:RecordDetailPanelopens its inline-edit session on handler presence alone, so a caller without update is offered the editors (the overlay's Delete is behindrecordDeleteinDetailView), 3;MasterDetailForm's line grids' add / remove (a create-mode master's removed line is never a server delete, so the line rows do not apply as-is), 1;These are recorded, not taken: per the dispatch, a large unmapped backlog is a decision for the seat, not silent scope growth. Each needs its reach measured first.
ImportWizard's legacy per-row create is behindlistImporton the console list; the AI build panel's spreadsheet import (ExcelImportBar) mounts the same wizard with no grant read (noted on that ledger entry).The card move and the reschedule ask the object grant, not the field question on the field they write (
groupBy, the date fields); a field the permission set markseditable: falseis still refused only by the server.A line-items panel under a create-only child grant: Add is offered, while the grid's cells ask the edit question for every row (the column gate is per column, not per row), so a new line's cells are locked. That is unchanged by this PR; a per-row create question needs per-row column gating in
GridField.A remove-only gate is whole-grid: under a grant without delete, a just-added, unsaved line cannot be removed before Save either.
Changeset
.changeset/12082-write-affordance-census.md:@object-ui/coreminor (three new rows widen the exportedAFFORDANCE_GRANTSand theConsoleAffordanceunion; nothing removed or renamed); plugin-view, plugin-calendar, plugin-kanban, plugin-form patch.Generated by Claude Code