Repository navigation
fix(core,app-shell,plugin-detail): freeze the write census's backlog; the record feed and a related list's Add existing / fallback remove read the affordance-to-grant map (objectui#12082) - #12140
Conversation
…ive its host-gated sites (objectui#12082) The census's 13 `unmapped` WRITE_SITES entries are now a closed list, UNMAPPED_BACKLOG, held equal to the ledger both ways: a NEW `unmapped` entry turns the census red until the site is graded, and a key graded out of the backlog must leave the list, so the backlog only shrinks. Leg A's hand-written HOST_GATED list is replaced by a derivation: a mapped site is host-gated when a file it names as a reader mounts the component the write is written in, and then every mount must be a file the site names. On this tree it finds the one site the list held (the import wizard); what it cannot see (a host that reaches the write through a hook, a function, an alias or a wrapper) is stated beside it. Claude-Session: https://claude.ai/code/session_01TYgwmFK1q4KJ6Qq2WRLzsD Co-authored-by: Claude <noreply@anthropic.com>
…read the affordance-to-grant map (objectui#12082) Leg B of the write census, the feed's five sites. The record page handed the feed's comment composer and reaction control their handlers unconditionally, so a member whose sets grant neither sys_comment nor sys_comment_reaction (the everyone baseline names neither; an application grants them) was offered a post the server refused at its object CRUD step. Four rows join AFFORDANCE_GRANTS, each asked of the object it writes: feedComment (create on sys_comment: the composer and the reply composer), feedReactionAdd and feedReactionRemove (create and delete on sys_comment_reaction: the reaction control and its take-back), and feedReactionColumn (update on sys_comment: the reaction control on a deployment with no sys_comment_reaction). The record page hands each handler over only where its row allows it; a take-back click with the remove row closed writes nothing. No provider: every grant reads open. The five census entries move to mapped and leave the frozen backlog. Claude-Session: https://claude.ai/code/session_01TYgwmFK1q4KJ6Qq2WRLzsD Co-authored-by: Claude <noreply@anthropic.com>
… read the affordance-to-grant map (objectui#12082) Leg B of the write census, the related list's three sites. Add existing (the `add` picker) read no grant. Measured where its write goes, it is one of two, each a row the map already has: with a `linkField` it creates a link row of the list's own object (relatedNew, create on the list object); without one it re-parents the picked record (relatedRowEdit, update on the picked object). RelatedList withholds the button and its dialog where that row is closed. The fallback remove (an `add` config, no host delete) deleted with no grant read, and on a record page it put back exactly the delete the host bridge had withheld for a closed relatedRowDelete. It now reads relatedRowDelete of the list object, with the object's registered schema, so it can never re-open what the host closed. No host and no permission provider: still offered, as before. The three census entries move to mapped and leave the frozen backlog. Claude-Session: https://claude.ai/code/session_01TYgwmFK1q4KJ6Qq2WRLzsD Co-authored-by: Claude <noreply@anthropic.com>
… measured (objectui#12082) Leg B's dashboard designer site. Its Save writes `sys_dashboard`, a per-type table objectui retired (dashboards persist through /meta, ADR-0034) and one no objectstack server registers: it is absent from the spec's platform object registry, OSS and cloud alike. So the write fails for every caller whatever their grant, and no row can make the affordance honest; no in-app link reaches the route either. The entry leaves the frozen backlog as outOfFamily with that reading; the four Setup assignment entries are what remains. Claude-Session: https://claude.ai/code/session_01TYgwmFK1q4KJ6Qq2WRLzsD Co-authored-by: Claude <noreply@anthropic.com>
…sting and fallback remove, and the frozen backlog (objectui#12082) The core README names the four feed rows and the related list's reuse of relatedNew / relatedRowEdit, and says the census backlog is frozen. The plugin-detail README and docs page say which grant the record feed's composer, reply input and reaction control need, and which row a related list's Add existing and its fallback remove read. Changeset: @object-ui/core minor (four rows widen the published AFFORDANCE_GRANTS), @object-ui/app-shell and @object-ui/plugin-detail patch. Claude-Session: https://claude.ai/code/session_01TYgwmFK1q4KJ6Qq2WRLzsD Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TYgwmFK1q4KJ6Qq2WRLzsD 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
|
Claude-Session: https://claude.ai/code/session_01TYgwmFK1q4KJ6Qq2WRLzsD 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: Inputs: card objectui#12082 (body and all 20 comments, the dev's report Shape: draft on ① Derived judgments
No accept-set is narrowed and no published signature changes anywhere in the diff. ② Semver level
③ Boundary flagsThe dev's report
Reviewer-found, carried as notes, not conditions:
The four Setup entries stay Implemented-by: VERDICT: PASS |
Part of #12082 (the census freeze and leg B's non-Setup sites; the four Setup entries stay on the card)
Clause-②: yes
Claim
6108058067(seatdomain:ui#2), triage6102063887. Implemented by anos-devrun dispatched from sessionhttps://claude.ai/code/session_01TYgwmFK1q4KJ6Qq2WRLzsD.Clause-②isyes, not the claim's predictedno: four rows joinAFFORDANCE_GRANTS, a published value of@object-ui/core, so core isminor. The contract review atCONTRACT_REVIEW_TIERis owed.What lands, in triage's order
1. The backlog freeze, and leg A's
HOST_GATEDlimit closedUNMAPPED_BACKLOG(the census pinaffordanceGrantMap-12082.test.tsx) is a closed list of the keysWRITE_SITESmay gradeunmapped, held equal to the ledger both ways. A NEWunmappedentry is red until graded; a key graded out ofunmappedmust leave the list. It was frozen at the 13 keys on BASE, and each later commit deletes the keys it grades. At this head it holds the four Setup assignment keys only.HOST_GATEDlist is replaced by a derivation,hostGatedSites: a mapped site is host-gated when a file it names inreadByJSX-mounts the component the write is written in, and then every mount must be inreadBy. Measured on this tree it finds exactly the site the list held (the import wizard). So a NEW host-gated component needs no list entry to be seen. What it cannot see is written beside it: a host that reaches the write through a hook, a function, an alias or a wrapper component, not a JSX element of the component's own name.2. Leg B: the nine non-Setup sites, reach measured per site
handleAddComment,handleAddReply(createsys_comment)everyonebaseline grants nosys_commentbit (an app grants it). Read on objectstackmain67b669e6:member_defaultnames nosys_comment;viewer_readonlyhas'*'read with no create; the showcase app's sets namesys_commentzero times; the security middleware's step "2. CRUD permission check" maps insert toallowCreateand refuses with403 PERMISSION_DENIEDfeedComment(create)handleToggleReaction > writecreate / delete (sys_comment_reaction)feedReactionAdd(create) andfeedReactionRemove(delete)handleToggleReactionupdate (sys_commentcolumn store)sys_comment_reactionfeedReactionColumn(update)saveSchema(updatesys_dashboard)sys_dashboardis a per-type table objectui retired (dashboards persist through/meta, ADR-0034, the header ofruntime-metadata-persistence.ts), and objectstackmainregisters no such object (absent from the spec'sPLATFORM_OBJECTS_BY_PACKAGEandCLOUD_PROVIDED_OBJECT_NAMES; the repo tree has no dashboard object file, controlsys-comment-reaction.object.ts1 hit). The Save fails for every caller whatever the grant, and no in-app link reachesdesign/dashboard/NAMEhandleAddRecordscreate (linkFieldset)relatedNew(reused: create on the list's object)handleAddRecordsupdate (nolinkField)add.picker.object)relatedRowEdit(reused: update on that object)record:related_list: the fallback removeonDeleteexactly whenrelatedRowDeleteis closed, and the fallback (anaddconfig, no host delete) then put the delete straight backrelatedRowDelete(reused), read with the list object's registered schema, so it never re-opens what the host closedH4's two candidates were both right, one per branch: the junction branch is
relatedNewon the junction, the re-parent branchrelatedRowEditon the picked object.The record feed, how it reads the rows
RecordDetailViewhands each handler to the discussion context only where its row allows it, and the panel draws the composer, the reply input and the reaction picker from the handler's presence. Each row is asked of the object it writes, with that object's schema from the registry, so policy and the effective operation set are read too. On the records store the control is the add row; a take-back click asks the remove row and, closed, writes nothing (no write the server would refuse, and nothing shown changed). With no permission provider every grant reads open, as before.Files
packages/core/src/utils/affordanceGrants.ts: four rows.packages/app-shell/src/views/RecordDetailView.tsx:resolveFeedWriteGatesand the handler hand-over.packages/plugin-detail/src/RelatedList.tsx:addGrantedgates the Add button and its dialog.packages/plugin-detail/src/renderers/record-related-list.tsx:removeFallbackGranted.packages/core/README.md,packages/plugin-detail/README.md,content/docs/plugins/plugin-detail.mdx(objectui AGENTS.md rule 2).AssignedUsersSection.tsx,PositionHoldersSection.tsxand their entries (deferred to the objectui#7611 epic's C9), andDashboardDesignPage.tsx(graded, not changed).Evidence
c9fc10b1, restore-trapped): the four source files checked out at BASE (4/4 blobs equal BASE on disk, each fix anchorgrep -c0), the HEAD pins and census run against them:Test Files 4 failed (4),Tests 20 failed | 64 passed (84). Red per pin, every red in the defect direction (an affordance offered under a grant that does not hold it): feed 9 of 18, Add existing 5 of 9, fallback remove 4 of 8, census 2 of 49 (the row table and the mapped-site check). Controls stayed green (no provider; the full shape; the host's own delete). Restore: 4/4 blobs equal HEAD,git diff HEADempty, status clean.scripts/ablation-replace.mjsWRAP mode, predictions written first, each restoreblob == HEADandgit diff HEADempty:unmapped: census 1 failed / 48 passed, the freeze test (added). As predicted.UNMAPPED_BACKLOG: census 1 failed / 48 passed, the freeze test (left). As predicted.ImportWizardin a file that reads nolistImport: census 1 failed / 48 passed, the host-gated mount test. As predicted.onAddCommentover unconditionally: feed pin 4 failed / 14 passed (the four closed composer shapes); the census stays green, since the file still readsfeedComment. As predicted: the runtime pin catches what the census cannot.relatedRowEdit: 3 failed (census: no file it names readsrelatedNew; Add pin: the create-only and edit-only junction shapes). As predicted.os-dev-reportcomment on the card. At open: pins + census green on the main merge (84 passed); all 48RecordDetailView*files312 passed;plugin-detailshard 1/4713 passed | 8 skipped;type-checkofcore,plugin-detail,plugin-formexit 0, each echoing its own script, 0error TS; eslint on the 8 touched.ts/.tsx0 errors, and each modified source file's warning count equal to BASE.Acceptance notes
RecordActivityTimelinedrawsThreadedReplies(and the reply input) only whenreplyCountis above 0, and nothing derivesreplyCounton read: the record page sets it only after a reply is posted in the session.sys_comment's own docblock says the count is taken fromparent_idchildren at read time. So a reply read back from the server is not shown, and the reply input appears on no read row. The reply site is still mapped tofeedComment(same row and gate as the composer). Noted, not filed: no reach measured at a door.outOfFamilywith the reading above. The route has no in-app entry (the dashboard view's top-bar "Design in Studio" deep-links to Studio's own surface), so it is reached by URL or the recent list only, and a Save there fails silently (saveSchemaswallows the error). Noted, not filed.sys_comment/sys_comment_reactionschemas from the page'sobjects; absent, the default bucket is read, so the grant and the operation set still decide.Gate lead (hand-derived, objectui has no
dispatch-gates)Build closure
turbo run buildforapp-shell^...,plugin-detail^...,plugin-form^...;type-checkforcore,app-shell,plugin-detail,plugin-form; vitest for those packages (sharded), the census and every-12082pin; the two repo ratchets andscripts/__tests__/; the changeset, citation, control-byte, test-path,vi-mock, phantom-deps and doc gates.Generated by Claude Code