You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Sharing Rules list shows the recipient's and object's labels, not api names #12613
Walked on objectui main 8415d9f11, /apps/com.objectstack.setup/sys_sharing_rule (11 rows from HotCRM): Object crm_account, Recipient Type position, Recipient sales_manager / eu_sales_team / escalated_from, Access Level edit. The same rule in Studio's Access pillar reads "Account", "Sales Manager".
Target
Object column: the object's icon and label (api name in the tooltip).
Recipient: the position / team / user label resolved through the same lookup the Access pillar uses; the type as a small chip before it ("Position · Sales Manager", "Field · Escalated from").
Access level as a chip (Read / Edit) with the same tokens the permission matrix uses.
File surface
The sys_sharing_rule list view definition the console ships (or, if the columns come from the platform's view, objectstack-ai/objectstack's platform-objects view — the card says so and files it there), the cell renderers in packages/plugin-grid / plugin-list for metadata references, tests beside.
Acceptance
Tests: a rule with recipient sales_manager renders "Sales Manager".
Screenshot at 1280 px.
pnpm --filter @object-ui/app-shell test green.
Constraints
The stored values do not change; display only.
Evidence
No screenshot; the measurements above and the page URLs are the evidence.
Filed by the epic PM session session_cdb7a717-def7-4e71-9988-f089fbaa18ff from the 2026-10-16 second review pass: objectui main 8415d9f11, HotCRM 63db83e2, the vite console on :5180 against objectstack serve --dev on :4001, the Studio pillars and all 43 Setup pages walked in the maintainer's browser pane at 1024 × 768 (en) with a 1280 × 900 re-check (zh). The maintainer asked for every finding to be filed, verbatim: 「全部立卡」.
Claim: PM loop round 1
Session: session_01Vk15bz8xHtKGUfXfCm16h4
Account: os-elon-musk (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-12613-sharing-rule-labels
Worktree: objectui-issue-12613
Domain: domain:ui
Seat: domain:ui#3
File surface: packages/fields/src/ (the read-mode display of the declared object-ref and recipient-picker field widgets, ObjectRefField.tsx / RecipientPickerField.tsx, and resolveCellRendererType / getCellRenderer in index.tsx only where a declared widget picks the cell), the grid's cell call site in packages/plugin-grid/src/ only where a column's cell renderer is resolved (⛔ not the grouped rendering PR objectui#12633 changes), packages/plugin-list/src/ only if the list resolves cells apart from the grid, tests beside each, and one .changeset/12613-*.md (stop on breach; explain in the report). ⛔ No change to stored values, to objectstack's sys_sharing_rule definition, or a field-name-keyed table.
Container & model: M, mode:subagent, model: opus (default tier; dispatch-gates --tier --repo objectstack-ai/objectui over the named paths: no path-derived mandate)
Clause-②: no (display only, from widget hints the platform already declares; if the fix adds an exported prop or type to a published package, the dev measures the built declaration closure and this line is corrected)
Responsibility: n/a — not a defect card
Thread-read: none
Serial constraints cleared: PR objectui#12633 (objectui#12620, seat 4) is open on packages/plugin-grid/src/ObjectGrid.tsx and GroupRow.tsx for grouped rendering, a different region; whichever lands later merges main first. objectui#12622 (seat 2, claim 6209697624) is claimed on the date and date-time renderers in packages/fields/src/, a different widget family. No other open PR touches packages/fields/src/ (6 open objectui PRs' file lists read 2026-10-16T07:34Z).
os-dev-report
{
"issue": 12613,
"status": "done",
"branch": "claude/issue-12613-sharing-rule-labels",
"pr": "#12646",
"session": "session_01Vk15bz8xHtKGUfXfCm16h4",
"premise_still_valid": true,
"summary": "A textual field declaring widget 'object-ref' or 'recipient-picker' now picks a read cell (resolveCellRendererType promotes on the declared widget, never on an object or field name). The object cell draws the object's label with the picker's 'Label (name)' spelling as tooltip; the recipient cell names users/teams/business units/positions/user columns from the row's recipient_type, handed over as a new optional CellRendererProps.dependentValues. Both reuse the edit widgets' own reads (catalog loader and kind table moved into shared modules), batched across rows: one catalog read per data source, one $in query per directory object, the metadata store for positions. Unresolvable values draw muted with the shared unresolved mark. ObjectGrid passes the widget hint and the row on column paths A-D, the link cells and the inline record panel. Not done, with reasons in the PR: object icon (catalog carries no icon), the type chip before the recipient (the adjacent recipient_type column already shows it; a copy would be a second derivation), and access-level tokens (select cell already draws a chip; the matrix tones are private to PermissionMatrixEditor).",
"tests": "All on merged head f4334e9. New: packages/fields/src/tests/widgetRefCells-12613.test.tsx (10 cases, includes recipient sales_manager renders 'Sales Manager' and the object column renders 'Account' with title 'Account (crm_account)') and packages/plugin-grid/src/tests/widgetHintedColumns-12613.test.tsx (paths A-D with a plain-text control column). pnpm exec vitest run --maxWorkers=2 packages/fields/ packages/plugin-grid/ packages/plugin-detail/ -> 'Test Files 751 passed | 2 skipped (753)', 'Tests 8776 passed | 21 skipped (8797)', exit 0 (VERDICT command-exit 0, lock held 38m on a shared box). Ablation 1 on the committed fix via scripts/ablation-replace.mjs (anchor 1->0, blob 41d9c158a162->0e19916f6a39, promotion guard disabled): 'Tests 11 failed | 3 passed (14)', the 3 green being the unchanged-face cases as predicted; restore proven (blob == HEAD, git diff HEAD empty). Ablation 2 (path B type cell without dependentValues): only 'B' red, 'Tests 1 failed | 3 passed (4)'; restore proven. No build/dist leg: vitest aliases @object-ui/fields to src. Direction observed: turned red, as predicted.",
"gates": "Local (hand-derived for objectui; objectui has no dispatch-gates.mjs, so no --ran reconciliation): turbo run build --filter='@object-ui/plugin-grid^...' --concurrency=2 BUILD_EXIT=0 on merged tree; pnpm --filter @object-ui/fields type-check 0; pnpm --filter @object-ui/plugin-grid type-check 0; pnpm --filter @object-ui/plugin-detail type-check 0 (fields tsconfig.test.json --listFiles counts the new test: 2 hits); eslint on the 13 changed source/test files with the repo config: exit 0, 0 errors (with --no-inline-config: 1 error at an untouched ObjectGrid line whose react-hooks/static-components disable comment that flag ignores, not this diff); node scripts/check-changeset-presence.mjs 0; pnpm check:new-line-citations 0 ('0 new citation(s)'); node scripts/check-governed-queue-guard.mjs --test NOT GOVERNED; control-byte grep on changed files: no match. i18n family not run: no locale file changed. CI on PR head f4334e9 at report time: label success; Test shards 1-16, Type Check, Lint, Build & E2E, Test (dist pins), Repo Gates, Changeset Declaration/Bump Policy/Fixed Group Check/Claim Re-read/Overwrite Report, Line Citation Gate, Governed Surface Queue Guard, Build Docs, Bundle Analysis, Live E2E (informational) all in_progress; coverage legs and dependabot skipped. NOT MEASURED: screenshot (1280 px), reason: no backend stack reachable (objectstack checkout has no node_modules or built packages); the seat runs the browser check at review.",
"mcp_calls": "0",
"api_writes": "2 relay strokes via scripts/pm/fleet-write/dispatch.mjs (each one POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create -> POST /repos/objectstack-ai/objectui/pulls (draft) + POST /repos//issues/12646/assignees; (2) comment -> POST /repos//issues/12613/comments (this report). git push of the branch (3 pushes) is not REST. No label writes.",
"files_changed": [
".changeset/12613-widget-ref-read-cells.md",
"packages/fields/README.md",
"packages/fields/src/index.tsx",
"packages/fields/src/widgets/ObjectRefField.tsx",
"packages/fields/src/widgets/RecipientPickerField.tsx",
"packages/fields/src/widgets/objectCatalog.ts",
"packages/fields/src/widgets/recipientKinds.ts",
"packages/fields/src/widgets/refLabelDirectory.ts",
"packages/fields/src/tests/widgetRefCells-12613.test.tsx",
"packages/fields/src/tests/cellRenderers.objectLiteral-8596.test.tsx",
"packages/plugin-grid/src/ObjectGrid.tsx",
"packages/plugin-grid/src/cellRendererResolution.ts",
"packages/plugin-grid/src/tests/widgetHintedColumns-12613.test.tsx",
"packages/plugin-detail/src/tests/summaryChip.badgeFitCensus-8464.test.tsx"
],
"line_budget": "n/a — no skills/** or governed ledger touched (governed-queue guard: NOT GOVERNED)",
"deviations": [
"File surface breached, both forced by the change and named in the PR body: packages/plugin-detail/src/tests/summaryChip.badgeFitCensus-8464.test.tsx (two census rows plus its fit-side count 36 -> 38; the census names every registered cell type and reds by name on a new one) and packages/fields/README.md (outside packages/fields/src; AGENTS.md #2 docs-driven).",
"Clause-② written as 'yes' at the PR body's line start, corrected from the claim's provisional 'no' per the claim's own instruction: built fields dist/index.d.ts gains CellRendererProps.dependentValues (optional) and a widget? member on resolveCellRendererType's object arm; plugin-grid dist/index.d.ts unchanged (0 references to cellRendererResolution). The claim comment itself still reads 'no'.",
"Branch history: fast-forwarded onto origin/main 619e3f7 (objectui#12633 landed) before the first edit, then one merge of origin/main 07b0632 before opening the PR; gates re-run on the merged tree. No conflict.",
"Two verify-lock acquisitions answered queue-timeout (exit 99, NOT MEASURED at the time); both measurements were re-taken later under the same slot and are the ones quoted.",
"The first pnpm install was started with a trailing & and its log read afterwards (it had finished, exit 0); every later heavy run was foreground or nohup + tail --pid.",
"The card's acceptance item pnpm --filter @object-ui/app-shell test was not run locally: app-shell is not touched; declared to CI's test shards."
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed — DetailSection (plugin-detail) now promotes object_name to the object cell through resolveCellRendererType but passes no row, so a record page's recipient_id still reads as text; one-line follow-up (dependentValues={data}). Dedupe words: detail recipient label, dependentValues DetailSection, recipient-picker read cell.",
"carrier: none · noted, not filed — the By Object list view's group band prints the object api name (band labels are strings built by the grid's grouping, not cells); read from source, not measured in a browser. Dedupe words: group band object label, by_object grouping api name, sharing rule group header.",
"carrier: none · noted, not filed — @objectstack/spec's FieldSchema.widget describe() calls it a 'Form widget override' while list cells now read it for object-ref / recipient-picker; docs face only. Dedupe words: widget describe form override, widget read cell, FieldSchema widget doc."
]
}
Claim: PM loop round 1 (re-stated by the same session on the same branch to correct the Clause-② line of claim 6210498989 and to add the two files the dev's report names; nothing else changes)
Session: session_01Vk15bz8xHtKGUfXfCm16h4
Account: os-elon-musk (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-12613-sharing-rule-labels
Worktree: objectui-issue-12613
Domain: domain:ui
Seat: domain:ui#3
File surface: as claim 6210498989 declared, plus the two files report 6212592185 names outside it: packages/plugin-detail/src/__tests__/summaryChip.badgeFitCensus-8464.test.tsx (the census names every registered cell type) and packages/fields/README.md (objectui AGENTS.md #2, docs-driven) (stop on breach; explain in the report)
Container & model: M, mode:subagent, model: opus (default tier; dispatch-gates --tier --repo objectstack-ai/objectui over the named paths: no path-derived mandate)
Clause-②: yes
Clause-② reason: the PR widens a published surface of @object-ui/fields, whose only entry is exports["."] → dist/index.d.ts. CellRendererProps gains the optional dependentValues?: Record<string, unknown>, and resolveCellRendererType's object argument gains widget?: string. Both were measured by the dev on the built dist/index.d.ts and re-read by the seat in the PR diff at head f4334e9. No @object-ui/types member and no authoring key change. The seat's TIER_DEFAULT review of record comes before ready.
Responsibility: n/a — not a defect card
Thread-read: 6212592185
Serial constraints cleared: PR objectui#12633 (objectui#12620) has landed, and this branch merged main after it. PR objectui#12641 (objectui#12622, seat 2) is open on packages/fields/src/index.tsx, the same file as this PR's cell resolution, in its date widget registration; whichever lands later merges main first and keeps both intents. No other open objectui PR touches packages/fields/src/ or packages/plugin-grid/src/ (all open PRs' file lists, read 2026-10-16T09:38Z).
os-dev-report
{
"issue": 12613,
"status": "done",
"branch": "claude/issue-12613-sharing-rule-labels",
"pr": "#12646",
"session": "session_01Vk15bz8xHtKGUfXfCm16h4",
"premise_still_valid": true,
"round": "patch round 1 (Bundle Analysis red on f4334e9)",
"summary": "I reproduced the failure locally. On the merged tree with this branch's directory, the ui-components chunk measured 289,748 gzip bytes against its 289,000 ceiling, over by 748. The PM hypothesis was refuted: the old directory did not get in through the lazy edit widgets. Every packages/fields module lands in the eager ui-components chunk whatever imports it, because the ui-components advancedChunks group captures by path; scripts/vite-ineffective-dynamic-imports.ts documents this. So no lazy import inside fields can move bytes out. A control build of main's fields src (merge-base 8ed7e5a, restore proven) measured 288,585, which leaves 415 bytes of headroom on main. Fix at the cause, following the RECORD_POINTER_CARD_TYPE arrangement. @object-ui/fields keeps only the rule: the widget promotion, the two keys registered with TextCellRenderer as their default face, and the optional CellRendererProps.dependentValues. It also publishes the edit widgets' reads and kind table (loadObjectCatalog, objectRefOptionLabel, RECIPIENT_RECORD_KINDS, RECIPIENT_REGISTRY_KINDS, FIELD_RECIPIENT_TYPE, loadRecipientCandidates, recipientCandidateLabel) and UnresolvedReferenceMark. @object-ui/app-shell registers the label faces under the two keys. The faces and their label directory load lazily, on the first cell that draws one, and land in a lazy chunk (WidgetRefCellFaces-.js) outside the eager closure. The directory also became one catalog mechanism: record kinds read the picker's own candidate query, now shared with the picker as loadRecipientCandidates, instead of a batched $in reader. No ceiling, budget file, allow-list or chunk group was touched.",
"tests": "All on merged head a2ed222, after merging origin/main twice this round (fb38bcd, then a2ed222). Targeted run: pnpm exec vitest run --maxWorkers=2 over 21 files (fields widgetRefCells-12613; app-shell WidgetRefCellFaces-12613; plugin-grid widgetHintedColumns-12613; the five fields cell censuses and listCellRendererTypes-8734; plugin-detail summaryChip.badgeFitCensus-8464; grid linkCellNoNestedAnchor-11817, formatHintedColumnRenderer-8920, cellRendererResolutionBoundary-8920, gridDependentValues-7165; RecipientPickerField.test and its fieldRecipient-7613; widget-aria-invalid / widget-dom-leak e2e; booleanField.readonlyNonBoolean-8593; app-shell product-neutral-renderer.ratchet, record-pointer-card-renderer, decisionOutputParams) -> 'Test Files 21 passed (21)', 'Tests 572 passed (572)', exit 0. Acceptance cases now live in app-shell: 'a rule with recipient sales_manager renders "Sales Manager", and its object renders "Account"' (through the real keys a declared widget resolves to; tooltip 'Account (crm_account)'). The grid test now uses probe faces to pin the grid's half of the contract: the widget key and the row (recipient_type) on paths A-D. Ablations on the committed rework via scripts/ablation-replace.mjs: A1, promotion guard disabled (anchor 1->0, blob cef8608ee3b2 -> 3d22c765db13): 'Tests 7 failed | 11 passed (18)', red in all three files as predicted (the default-text-face case stays green by design). A2, the recipient-picker registration deleted (blob 685e8575e11f -> 047eea89aba3): 'Tests 2 failed | 8 passed (10)'. Both restores proven (blob == HEAD, git diff HEAD empty). Not re-run: the full fields/plugin-grid/plugin-detail suites (last full run, round 0 on f4334e9: 751 files / 8776 tests green). This round's fields change returns the two keys to the text face main draws, and the reach was covered by the targeted set.",
"gates": "Reproduction (merged tree fb38bcd; fields dist built from the committed directory, timestamp before any edit; console aliases fields to src): turbo run build --filter='./packages/*' + pnpm --filter @object-ui/console build + node scripts/check-eager-closure-budget.mjs -> exit 1, 'ui-components 283.0 KB / 282.2 KB ceiling (OVER by 0.7 KB)' = 289,748 gzip bytes. Control (main's fields src at 8ed7e5a, console rebuilt, restored and proven): ui-components 288,585. After the fix, on final head a2ed222: PKGS_BUILD_EXIT=0; fields / app-shell / plugin-grid type-check TC_EXIT=0 each; CONSOLE_BUILD_EXIT=0; check-eager-closure-budget BUDGET_EXIT=0 ('Console eager closure is 3096.6 KB gzipped across 70 of 2253 chunks (budget: 3119.4 KB, headroom: 22.8 KB)', ui-components 288,954 / 289,000 gz, '282.2 KB / 282.2 KB ceiling (headroom 0.0 KB)'; chunk membership pass, fields 88 modules in ui-components); pnpm check:sdui-registration-pins exit 0; node scripts/check-side-effects-array.mjs 0 (app-shell sideEffects names the new registration module); node scripts/check-phantom-dependencies.mjs 0; eslint on the 16 changed .ts/.tsx files exit 0, 0 errors; node scripts/check-changeset-presence.mjs 0; pnpm check:new-line-citations 0 new; governed-queue guard NOT GOVERNED; control-byte grep: no match. On the earlier head 0458f1a (before the second merge) ui-components measured 288,744; the second main merge (objectui#12622's date picker among it) moved it +210. CI on PR head a2ed222 at report time: 0 check-runs registered yet (read right after the push); mergeable_state 'blocked', draft.",
"mcp_calls": "0",
"api_writes": "This round: 1 relay stroke via scripts/pm/fleet-write/dispatch.mjs (POST /repos/objectstack-ai/objectstack/dispatches) -> comment POST /repos//issues/12613/comments (this report). git push of the branch (1 push, a2ed222) is not REST. PR body not edited; no label writes. Round 0 writes as reported earlier (pr_create + assignees, report comment).",
"files_changed": [
".changeset/12613-widget-ref-read-cells.md",
"packages/app-shell/package.json",
"packages/app-shell/src/index.ts",
"packages/app-shell/src/views/widget-ref-cells/WidgetRefCellFaces.tsx",
"packages/app-shell/src/views/widget-ref-cells/tests/WidgetRefCellFaces-12613.test.tsx",
"packages/app-shell/src/views/widget-ref-cells/refLabelDirectory.ts",
"packages/app-shell/src/views/widget-ref-cells/widget-ref-cell-renderers.tsx",
"packages/fields/README.md",
"packages/fields/src/tests/cellRenderers.objectLiteral-8596.test.tsx",
"packages/fields/src/tests/widgetRefCells-12613.test.tsx",
"packages/fields/src/index.tsx",
"packages/fields/src/widgets/ObjectRefField.tsx",
"packages/fields/src/widgets/RecipientPickerField.tsx",
"packages/fields/src/widgets/objectCatalog.ts",
"packages/fields/src/widgets/recipientKinds.ts",
"packages/plugin-detail/src/tests/summaryChip.badgeFitCensus-8464.test.tsx",
"packages/plugin-grid/src/ObjectGrid.tsx",
"packages/plugin-grid/src/tests/widgetHintedColumns-12613.test.tsx",
"packages/plugin-grid/src/cellRendererResolution.ts"
],
"line_budget": "n/a — no skills/** or governed ledger touched (governed-queue guard: NOT GOVERNED)",
"deviations": [
"File surface widened to @object-ui/app-shell (packages/app-shell/src/views/widget-ref-cells/**, src/index.ts, package.json sideEffects), on the dispatch's own clause: when measurement shows the fix belongs in another package, fix it there and give the reason. The measured reason: fields cannot defer anything, because every fields module is group-captured into the eager ui-components chunk. The host-face arrangement is the existing RECORD_POINTER_CARD_TYPE precedent. app-shell's package.json sideEffects is a published-contract field, and the changeset now also lists '@object-ui/app-shell': minor.",
"Public surface of @object-ui/fields grew beyond round 0, measured on the built dist/index.d.ts (1 hit each): loadObjectCatalog, objectRefOptionLabel, RECIPIENT_RECORD_KINDS, RECIPIENT_REGISTRY_KINDS, FIELD_RECIPIENT_TYPE, loadRecipientCandidates, recipientCandidateLabel, UnresolvedReferenceMark (plus their types). CellRendererProps.dependentValues is unchanged from round 0. app-shell's dist/index.d.ts gains only the side-effect import, no export. The seat corrects the claim's Clause-② line.",
"Behaviour change versus round 0, for the PR body: the recipient record kinds are now named from the picker's candidate query (at most 500 rows ordered by name, read once per data source per kind), not a batched $in query. A record past the picker's first 500 reads muted-unresolved, exactly as the open picker does not offer it.",
"Two lock acquisitions in this round timed out (exit 99) before running; both were resumed on slot 12613 and measured. One reproduction build overlapped my edits. Its fields dist predates them (timestamp 09:54:48 against edits from 09:59). But its console build aliases fields to src, so its ui-components reading (289,748) may already include the first slimmer directory. The main control build (288,585) and the final readings are clean.",
"origin/main was merged twice this round: once at the start as the seat asked, and once more before the push, because objectui#12622 (fields date widgets) had landed and could only move the budgeted chunk. The final readings are all on that merged head."
],
"pr_body_changes": "The body is not edited here. For the seat: (1) 'What changed / @object-ui/fields' now describes fields as owning the promotion, the two keys with a text default face, CellRendererProps.dependentValues and the published reads/kind table plus UnresolvedReferenceMark. The object/recipient faces, the label directory and its once-per-data-source catalogs move to a new '@object-ui/app-shell' bullet: registered under the two keys, loaded lazily into WidgetRefCellFaces-.js outside the eager closure. (2) Replace 'record kinds collected for one microtask and read with one $in query per directory object' with 'record kinds named from the picker's own candidate query, read once per data source'. (3) Tests section: the acceptance cases are in packages/app-shell/src/views/widget-ref-cells/tests/WidgetRefCellFaces-12613.test.tsx; the grid test pins key + row with probe faces; the round-1 ablation figures are in this report. (4) Add a Bundle line: ui-components 288,954 / 289,000 gz on a2ed222; the main control at 8ed7e5a is 288,585. (5) Surface notes: add app-shell.",
"targets_1_2_answers": [
"Target 1 (object icon): no. The board's objectDef is the LISTED object's definition (sys_sharing_rule, icon 'shield-check'), so objectDef.icon names the wrong object. The referenced object's icon is in the console metadata store's object list, the same GET /meta/object the catalog reads, which carries full definitions with icon. The app-shell face could read it there without a second derivation. Not built.",
"Target 2 (type chip): yes, the board holds it. The grid already has objectDef.fields.recipient_type.options and translates them (translateOptions) for that column, so the chip's text exists without a second derivation. It does not reach the recipient face today, because a cell receives only its own field meta plus the row's raw values. The grid would have to hand over the sibling's option label. Not built."
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed. On merged head a2ed222, main plus this branch leaves the ui-components chunk 46 gzip bytes under its 289,000 ceiling (main alone: 288,585 at 8ed7e5a, plus about +210 from the latest main merge). The next fields-side change of any size will trip it. Ceiling capacity is the maintainer's to rule on; nothing was raised. Dedupe words: ui-components ceiling headroom, eager closure per-chunk budget fields, check-eager-closure-budget ui-components.",
"carrier: none · noted, not filed. DetailSection (plugin-detail) resolves object_name to the object-ref key and now draws the app-shell face there, but it passes no row, so a record page's recipient_id reads as text. Fix: dependentValues={data}. Dedupe words: detail recipient label, dependentValues DetailSection.",
"carrier: none · noted, not filed. The By Object view's group band prints the object api name (band labels are strings built by the grid's grouping); read from source, not measured. Dedupe words: group band object label, by_object grouping api name.",
"carrier: none · noted, not filed. @objectstack/spec FieldSchema.widget describe() says 'Form widget override', while list cells now read it for these two hints; docs face only. Dedupe words: widget describe form override, FieldSchema widget doc."
]
}
Claim: PM loop round 2 (re-stated by the same session on the same branch: the file surface moves the label faces into @object-ui/app-shell, on the dev's measurement in report 6214194030; the Clause-② reason is updated)
Session: session_01Vk15bz8xHtKGUfXfCm16h4
Account: os-elon-musk (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-12613-sharing-rule-labels
Worktree: objectui-issue-12613
Domain: domain:ui
Seat: domain:ui#3
File surface: as claim 6212639515 declared, plus packages/app-shell/src/views/widget-ref-cells/** (the lazily loaded object and recipient read faces and their label directory, registered under the two @object-ui/fields keys on the RECORD_POINTER_CARD_TYPE pattern), packages/app-shell/src/index.ts (the side-effect registration import) and packages/app-shell/package.json (sideEffects). The measured reason: every packages/fields module is captured by path into the eager ui-components chunk, so no face there can be deferred (stop on breach; explain in the report)
Container & model: M, mode:subagent, model: opus (default tier; dispatch-gates --tier --repo objectstack-ai/objectui over the named paths: no path-derived mandate)
Clause-②: yes
Clause-② reason: @object-ui/fields (entry exports["."]) gains the optional CellRendererProps.dependentValues, widget? on resolveCellRendererType's argument, and the reads and kind table the app-shell face imports. Patch round 2 cuts these to what app-shell actually imports, and the final names are read on the built dist/index.d.ts in the report. @object-ui/app-shell moves a published contract field (sideEffects) and exports nothing new. The seat's TIER_DEFAULT review of record comes before ready.
Responsibility: n/a — not a defect card
Thread-read: 6214194030
Serial constraints cleared: packages/app-shell/src/views/widget-ref-cells/** is new; app-shell/src/index.ts and package.json are named by no other in-flight lane claim or open PR (read 2026-10-16T11:11Z). PR objectui#12641 (packages/fields/src/index.tsx) has landed, and this branch merged it.
Correction to claim 6214243680, Serial constraints cleared: line (same session, 2026-10-16T11:12Z). packages/app-shell/package.json IS also open in PR objectui#12648 (objectui#12626, seat 4, the 18.0.0-next.2 bump). That PR changes the @objectstack/spec dependency range; this card changes sideEffects, a different field. Whichever lands later merges main first. The rest of the claim stands.
os-dev-report
{
"issue": 12613,
"status": "done",
"branch": "claude/issue-12613-sharing-rule-labels",
"pr": "#12646",
"session": "session_01Vk15bz8xHtKGUfXfCm16h4",
"premise_still_valid": true,
"round": "patch round 2 (head d47fb07)",
"summary": "(1) Record recipients resolve by exact id again, inside the lazy app-shell directory. Every user / team / business-unit id any cell asks for in one tick goes out as one $in query per kind (chunked at 100). The directory caches the row, and the face builds the label at render with withoutDeniedFields(row, usePermissions(), object, [storeField]), so the fields the viewer may not read are dropped as the lookup picker drops them, and a policy that loads later relabels the cell. A recipient past the picker's first 500 candidates is named. (2) The object face reads the console metadata store's object list, ensureType('object'): the same GET /meta/object list the picker offers, as full definitions. Label and icon come from that one session-cached read, with no request of its own. The icon is drawn the way the console's navigation draws an object's: LazyIcon, size-3.5, muted, with the Database glyph as fallback. (3) @object-ui/fields publishes only what app-shell imports. loadObjectCatalog and loadRecipientCandidates are gone, and the widgets keep their own reads inline exactly as on main; objectCatalog.ts became objectRefLabel.ts, holding only the shared Label (name) spelling. The fields diff against main is now the promotion, the two text-default keys, the optional dependentValues prop, the kind table moved into recipientKinds.ts, and six published names.",
"tests": "On head d47fb07 (main 8614705 merged; origin/main has since moved, see deviations). Targeted run: pnpm exec vitest run --maxWorkers=2 over the same 21 files as round 1 -> 'Test Files 21 passed (21)', 'Tests 572 passed (572)', exit 0. The app-shell face file, verbose: 11 passed. They include 'names record recipients by EXACT id — one past the first 500 candidates too — with one $in query per kind', which pins: a 600-user directory whose candidate query returns only the first 500; usr_550 named 'User 550'; exactly 2 find calls (sys_user $in [usr_550, usr_7, usr_gone] with $top 3; sys_team $in [team_eu]); usr_gone muted. Also 'leaves the fields the viewer may not read out of a record label, as the lookup picker does': PermissionProvider denies sys_user.name, so the label reads 'user550@example.com' and not 'User 550'. And 'draws the object's icon and label …': svg.lucide-building for crm_account (icon 'building'), svg.lucide-database for crm_contact (no icon), one ensureType('object') call. Ablations via scripts/ablation-replace.mjs on the committed head, each restore proven (blob == HEAD, git diff HEAD empty). A3, the record read put back on the candidate query ($top: 500, $orderby: name, blob 83e934f1fcf1 -> 3a9ae8d66290): 'Tests 2 failed | 9 passed (11)'. Both reds are expected: the exact-id case, and the policy case, whose usr_550 is also off the candidate page. A4, icon dropped (name={undefined}): 'Tests 1 failed | 10 passed (11)', the icon case. A5, withoutDeniedFields removed: 'Tests 1 failed | 10 passed (11)', the policy case. Observed direction: turned red, as predicted.",
"gates": "On head d47fb07: turbo run build --filter='./packages/*' --concurrency=2 PKGS_BUILD_EXIT=0; fields / app-shell / plugin-grid type-check TC_EXIT=0 each; pnpm --filter @object-ui/console build CONSOLE_BUILD_EXIT=0; node scripts/check-eager-closure-budget.mjs BUDGET_EXIT=0. Its reading: 'Console eager closure is 3096.6 KB gzipped across 70 of 2253 chunks (budget: 3119.4 KB, headroom: 22.8 KB)', and ui-components at 288,905 / 289,000 gzip bytes ('282.1 KB / 282.2 KB ceiling (headroom 0.1 KB)'), which is −49 against round 1's a2ed222 reading of 288,954. The faces chunk WidgetRefCellFaces-.js (4,572 raw bytes) is not in the eager closure. pnpm check:sdui-registration-pins exit 0; node scripts/check-side-effects-array.mjs 0; node scripts/check-phantom-dependencies.mjs 0; eslint on the changed .ts/.tsx files exit 0 with 0 errors; node scripts/check-changeset-presence.mjs 0; pnpm check:new-line-citations 0 new; governed-queue guard NOT GOVERNED; control-byte grep: no match. No ceiling, budget file, allow-list or chunk group was touched. CI on d47fb07: not read yet at the time of this comment; the final message carries the reading.",
"published_surface": "Measured on the built packages/fields/dist/index.d.ts. Six new exported names, 1 hit each: UnresolvedReferenceMark (export declare function), objectRefOptionLabel (re-exported from ./widgets/objectRefLabel.js), and RECIPIENT_RECORD_KINDS, RECIPIENT_REGISTRY_KINDS, FIELD_RECIPIENT_TYPE and recipientCandidateLabel (re-exported from ./widgets/recipientKinds.js). Two widened existing members: CellRendererProps.dependentValues (optional, a string-keyed Record of unknown) and the widget?: string member on resolveCellRendererType's object arm. No longer exported (0 hits): loadObjectCatalog, loadRecipientCandidates, ObjectHeader, ObjectCatalogSource, RecipientMapping, RegistryRecipientMapping; the two mapping interfaces are named only inside recipientKinds.d.ts. @object-ui/app-shell's dist/index.d.ts gains only the side-effect import, no export. @object-ui/plugin-grid's published closure is unchanged.",
"mcp_calls": "0",
"api_writes": "This round: 1 relay stroke via scripts/pm/fleet-write/dispatch.mjs (POST /repos/objectstack-ai/objectstack/dispatches) -> comment POST /repos//issues/12613/comments (this report). git push of the branch (1 push, d47fb07) is not REST. PR body not edited; no label writes.",
"files_changed": [
".changeset/12613-widget-ref-read-cells.md",
"packages/app-shell/package.json",
"packages/app-shell/src/index.ts",
"packages/app-shell/src/views/widget-ref-cells/WidgetRefCellFaces.tsx",
"packages/app-shell/src/views/widget-ref-cells/tests/WidgetRefCellFaces-12613.test.tsx",
"packages/app-shell/src/views/widget-ref-cells/refLabelDirectory.ts",
"packages/app-shell/src/views/widget-ref-cells/widget-ref-cell-renderers.tsx",
"packages/fields/README.md",
"packages/fields/src/tests/cellRenderers.objectLiteral-8596.test.tsx",
"packages/fields/src/tests/widgetRefCells-12613.test.tsx",
"packages/fields/src/index.tsx",
"packages/fields/src/widgets/ObjectRefField.tsx",
"packages/fields/src/widgets/RecipientPickerField.tsx",
"packages/fields/src/widgets/objectRefLabel.ts",
"packages/fields/src/widgets/recipientKinds.ts",
"packages/plugin-detail/src/tests/summaryChip.badgeFitCensus-8464.test.tsx",
"packages/plugin-grid/src/ObjectGrid.tsx",
"packages/plugin-grid/src/tests/widgetHintedColumns-12613.test.tsx",
"packages/plugin-grid/src/cellRendererResolution.ts"
],
"line_budget": "n/a — no skills/** or governed ledger touched (governed-queue guard: NOT GOVERNED)",
"deviations": [
"origin/main was merged at the start of the round; it was already at 8614705, so the merge was a no-op. Before the push, main had moved to a5bb7ac (objectui#12626 deps v18 bump, objectui#12605 app-shell). Those commits touch neither packages/fields nor packages/components, and git merge-tree --write-tree HEAD origin/main reports no conflict (exit 0). Under the merge-only-for-conflict-or-main-red rule I did not merge again. The branch reads one merge behind, and the merge queue will rebuild it on main.",
"The object face no longer reads the picker's getObjects catalog: it reads the console metadata store, which serves the same /meta/object list and carries the icon. Outside a MetadataProvider the face draws the stored text, as the registry recipient kinds always have.",
"The object-icon tests rely on LazyIcon's lucide DynamicIcon resolving inside vitest (svg.lucide-building / svg.lucide-database found under waitFor). That is the same mechanism the console's navigation uses, so the pin reads the real glyph, not a mock."
],
"accepted_deviations_for_the_seat": [
"Target 2, the type chip before the recipient: not built. Every list view that shows recipient_id also shows the recipient_type column beside it, and the chip's text is that sibling select's translated option label. A cell receives only its own field meta plus the row's raw values, so drawing the chip would need the grid to hand over the sibling's option label.",
"Target 3, access-level tokens: not built. access_level already draws as a chip through SelectCellRenderer from its option labels. The platform declares no option colour, and the permission matrix's tones are private functions in PermissionMatrixEditor (app-shell), so a generic cell is not coupled to them."
],
"pr_body_lines_for_seat": [
"Line 2: Clause-②: yes — the published fields closure gains six names (UnresolvedReferenceMark, objectRefOptionLabel, RECIPIENT_RECORD_KINDS, RECIPIENT_REGISTRY_KINDS, FIELD_RECIPIENT_TYPE, recipientCandidateLabel) and widens two members (CellRendererProps.dependentValues, resolveCellRendererType's widget arm), measured on dist/index.d.ts. app-shell and plugin-grid published closures unchanged.",
"What changed / @object-ui/fields: the widget promotion; object-ref and recipient-picker registered with the stored text as their default face (the RECORD_POINTER_CARD_TYPE arrangement); the optional CellRendererProps.dependentValues; the kind table moved into recipientKinds.ts and published with recipientCandidateLabel, objectRefOptionLabel and UnresolvedReferenceMark for the host face. The edit widgets list and store exactly what they did.",
"What changed / @object-ui/app-shell (new): registers the two faces under those keys and loads them, with their label directory, lazily into WidgetRefCellFaces-.js, outside the eager closure. The object face draws the icon and label from the console metadata store's object list (ensureType('object')). The recipient face names record kinds by exact id, one $in query per kind per tick with denied fields removed (withoutDeniedFields); positions come from the metadata store and the field kind from the shared object's schema.",
"What changed / @object-ui/plugin-grid: unchanged from round 0. The widget hint and the row are passed on paths A-D, the link cells and the inline record panel.",
"Why app-shell: every packages/fields module rides the eager ui-components chunk whatever imports it (scripts/vite-ineffective-dynamic-imports.ts), and that chunk's per-chunk ceiling refused the label reader (289,748 / 289,000 measured on round 0's head). Main alone sat at 288,585 at 8ed7e5a.",
"Bundle: ui-components 288,905 / 289,000 gzip on d47fb07; eager closure 3096.6 KB of 3119.4 KB; the faces chunk is lazy.",
"Tests: the acceptance cases live in packages/app-shell/src/views/widget-ref-cells/tests/WidgetRefCellFaces-12613.test.tsx ('sales_manager' renders 'Sales Manager'; the object renders its icon and 'Account' with tooltip 'Account (crm_account)'; a recipient past the first 500 candidates is named by exact id; a denied name field is left out of the label). packages/plugin-grid/src/tests/widgetHintedColumns-12613.test.tsx pins key + row on paths A-D with probe faces. packages/fields/src/tests/widgetRefCells-12613.test.tsx pins the promotion, the default faces and the published spellings.",
"Card targets: 1 done (icon + label + api-name tooltip); 2 type chip and 3 access-level tokens are accepted deviations, with the reasons above.",
"Surface notes: app-shell added (widget-ref-cells/**, src/index.ts, package.json sideEffects); the plugin-detail census rows and the fields README as before.",
"Remove from the body: every mention of loadObjectCatalog, loadRecipientCandidates, 'one $in query per directory object' read by fields, and the round-0 statement that the fields cells draw the labels."
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed. The ui-components chunk has 95 gzip bytes of headroom on d47fb07 (288,905 / 289,000; main alone was 288,585 at 8ed7e5a, before the latest main merges). The next fields-side change of any size will trip it. Ceiling capacity is the maintainer's to rule on; nothing was raised. Dedupe words: ui-components ceiling headroom, eager closure per-chunk budget fields.",
"carrier: none · noted, not filed. DetailSection (plugin-detail) resolves object_name to the object-ref key and draws the app-shell face, but passes no row, so a record page's recipient_id reads as text. Fix: dependentValues={data}. Dedupe words: detail recipient label, dependentValues DetailSection.",
"carrier: none · noted, not filed. The By Object view's group band prints the object api name (band labels are strings built by the grid's grouping); read from source, not measured. Dedupe words: group band object label, by_object grouping api name.",
"carrier: none · noted, not filed. @objectstack/spec FieldSchema.widget describe() says 'Form widget override', while list cells now read it for these two hints; docs face only. Dedupe words: widget describe form override, FieldSchema widget doc."
]
}
os-dev-report
{
"issue": 12613,
"status": "done",
"branch": "claude/issue-12613-sharing-rule-labels",
"pr": "#12646",
"session": "session_01Vk15bz8xHtKGUfXfCm16h4",
"premise_still_valid": true,
"round": "merge round (new head f667f86)",
"summary": "I merged origin/main f78efdd into d47fb07 in a fresh worktree; the new head is f667f86, a merge commit with no rebase and no force-push. One file conflicted: packages/plugin-grid/src/ObjectGrid.tsx, against f78efdd (objectui#12608: list cells show the record a referenceVia pointer names). Its seven hunks are the same seven cell closures on both sides. Main prefixed each with pointerFace?.(row) ??; this branch added dependentValues={row} to the CellRenderer / LinkContentRenderer element. I resolved each hunk by keeping both: main's pointerFace?.(row) ?? prefix, followed by the RENDERER element carrying this branch's dependentValues={row} (the element's angle brackets are spelled out because the body sanitizer drops tag-shaped text). The resolved file has 0 conflict markers, 7 pointerFace?.(row) (the same count as main) and 7 dependentValues={row} (the same as the branch), and the record panel's dependentValues={record} is intact. packages/fields/src/index.tsx (a RECORD_POINTER_CARD_TYPE doc paragraph) and packages/app-shell/package.json (@objectstack/spec ^18.0.0-next.2) auto-merged. No generated file or lockfile conflicted; pnpm install was re-run for the v18 dependency bump.",
"tests": "On merged head f667f86. Targeted run: pnpm exec vitest run --maxWorkers=2 over the same 21 files plus main's packages/plugin-grid/src/tests/ObjectGrid.recordPointerCell-12608.test.tsx, which pins the closures the conflict touched -> 'Test Files 22 passed (22)', 'Tests 575 passed (575)', exit 0. No ablation: this round merged and changed no tested behaviour.",
"gates": "On merged head f667f86: turbo run build --filter='./packages/*' --concurrency=2 PKGS_BUILD_EXIT=0; fields / app-shell / plugin-grid type-check TC_EXIT=0 each; pnpm --filter @object-ui/console build CONSOLE_BUILD_EXIT=0; node scripts/check-eager-closure-budget.mjs BUDGET_EXIT=0. ui-components: 288,876 / 289,000 gzip bytes ('282.1 KB / 282.2 KB ceiling (headroom 0.1 KB)'), 124 bytes of headroom. Aggregate eager closure: 3,193,243 bytes, 'Console eager closure is 3118.4 KB gzipped across 70 of 2254 chunks (budget: 3119.4 KB, headroom: 1.0 KB)'; main's v18 bump moved vendor-objectstack to 1519.2 KB / 1519.2 KB (headroom 0.0 KB). framework 74.5 / 97.7 KB, i18n-locale-en 45.7 / 48.8 KB. Membership passes (fields 88 modules in ui-components, its src/widgets/date-picker subtree 2 in fields-date-picker). The faces chunk WidgetRefCellFaces-*.js is still outside the eager closure. pnpm check:sdui-registration-pins 0; node scripts/check-side-effects-array.mjs 0; eslint on the resolved ObjectGrid.tsx exit 0; node scripts/check-changeset-presence.mjs 0; pnpm check:new-line-citations 0. No ceiling touched. Two lock acquisitions timed out (exit 99; holder was slot 12082's verify1.sh, 20 minutes); the third, on the kept slot 12613, ran. CI on f667f86, read right after the push: 35 check-runs, 3 skipped (dependabot, two coverage legs), 31 in_progress, 1 queued, none completed yet. PR draft, mergeable_state 'behind', no longer 'dirty'.",
"merge_conflict": "packages/plugin-grid/src/ObjectGrid.tsx only, with main f78efdd (objectui#12608, list referenceVia pointer cells). Seven hunks: path A link+action, path A linked, path A type-only, path B primary link, path B type-only, path C, path D. Each was resolved as main's pointerFace?.(row) ?? prefix, followed by the RENDERER element with dependentValues={row}, keeping main's pointer face first and this branch's row hand-off on the fallback renderer.",
"mcp_calls": "0",
"api_writes": "This round: 1 relay stroke via scripts/pm/fleet-write/dispatch.mjs (POST /repos/objectstack-ai/objectstack/dispatches) -> comment POST /repos//issues/12613/comments (this report). git push of the branch (1 push, f667f86) is not REST. PR body not edited; no label writes.",
"files_changed_this_round": [
"packages/plugin-grid/src/ObjectGrid.tsx (conflict resolution in the merge commit)"
],
"line_budget": "n/a — no skills/** or governed ledger touched",
"deviations": [
"After the push, origin/main moved again to dcd1041 (three commits: objectui#12657 top bar, objectui#12661 plugin-detail, objectui#12659 Studio). git merge-tree --write-tree HEAD origin/main reports no conflict (exit 0), and in the budgeted packages they touch only one test file (packages/components/src/tests/iconOnlyButtonCensus-12143.test.ts, one deletion). Under the merge-only-for-conflict rule I did not merge again; the PR reads 'behind', not 'dirty'."
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed. On f667f86 the eager closure has 1.0 KB of aggregate headroom (3118.4 of 3119.4 KB), vendor-objectstack has 0.0 KB and ui-components 124 bytes. Main's v18 dependency bump consumed most of the aggregate. The next page-load change of any size in any eager chunk may trip the aggregate. Ceiling capacity is the maintainer's to rule on; nothing was raised. Dedupe words: eager closure aggregate headroom, vendor-objectstack ceiling v18, ui-components headroom."
]
}
State: pm:dispatched → pm:blocked on objectui#12649 · ACCEPT done, the landing waits on an informational lane · domain:ui seat 3 · session_01Vk15bz8xHtKGUfXfCm16h4 · 2026-10-16T13:44Z
What holds the landing:Live E2E (informational) is red on the head, as on every PR built on mainf62b6ff. Its anchor is objectui#12649 (domain:devx, p1). The seat's queue-entry rule reads every check, so the PR stays draft. This is the "work done, PR held by an external gate defect" use of this state. The claim, the assignee and the branch stay.
Watch on unlock: the eager closure has 1.0 KB of aggregate headroom on this head (ui-components 124 bytes). If main grows the closure before the anchor lands, the merge onto it may trip Bundle Analysis. objectui#12653 (in flight) carries the ceiling change.
On unlock: the seat re-reads the PR's head and checks. It merges main through the dev if the PR is conflicted or the budget moved, and lands the PR through the relay.
Re-derived before release: no new blocker. One watch: main gained a @object-ui/fields change (c6b7d82, the platform date picker in action dialogs), and this PR had 124 gzip bytes of ui-components headroom. Bundle Analysis on the new head decides it; if it reds, a dev round follows.
Activity
objectstack-fleet commentedon Oct 16, 2026
Claim: PM loop round 1
Session:
session_01Vk15bz8xHtKGUfXfCm16h4Account:
os-elon-musk(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-12613-sharing-rule-labelsWorktree:
objectui-issue-12613Domain:
domain:uiSeat:
domain:ui#3File surface:
packages/fields/src/(the read-mode display of the declaredobject-refandrecipient-pickerfield widgets,ObjectRefField.tsx/RecipientPickerField.tsx, andresolveCellRendererType/getCellRendererinindex.tsxonly where a declaredwidgetpicks the cell), the grid's cell call site inpackages/plugin-grid/src/only where a column's cell renderer is resolved (⛔ not the grouped rendering PR objectui#12633 changes),packages/plugin-list/src/only if the list resolves cells apart from the grid, tests beside each, and one.changeset/12613-*.md(stop on breach; explain in the report). ⛔ No change to stored values, to objectstack'ssys_sharing_ruledefinition, or a field-name-keyed table.Container & model:
M,mode:subagent,model: opus(default tier;dispatch-gates --tier --repo objectstack-ai/objectuiover the named paths: no path-derived mandate)Clause-②: no (display only, from widget hints the platform already declares; if the fix adds an exported prop or type to a published package, the dev measures the built declaration closure and this line is corrected)
Responsibility: n/a — not a defect card
Thread-read: none
Serial constraints cleared: PR objectui#12633 (objectui#12620, seat 4) is open on
packages/plugin-grid/src/ObjectGrid.tsxandGroupRow.tsxfor grouped rendering, a different region; whichever lands later mergesmainfirst. objectui#12622 (seat 2, claim6209697624) is claimed on the date and date-time renderers inpackages/fields/src/, a different widget family. No other open PR touchespackages/fields/src/(6 open objectui PRs' file lists read 2026-10-16T07:34Z).Generated by Claude Code
objectstack-fleet commentedon Oct 16, 2026
os-dev-report
{
"issue": 12613,
"status": "done",
"branch": "claude/issue-12613-sharing-rule-labels",
"pr": "#12646",
"session": "session_01Vk15bz8xHtKGUfXfCm16h4",
"premise_still_valid": true,
"summary": "A textual field declaring widget 'object-ref' or 'recipient-picker' now picks a read cell (resolveCellRendererType promotes on the declared widget, never on an object or field name). The object cell draws the object's label with the picker's 'Label (name)' spelling as tooltip; the recipient cell names users/teams/business units/positions/user columns from the row's recipient_type, handed over as a new optional CellRendererProps.dependentValues. Both reuse the edit widgets' own reads (catalog loader and kind table moved into shared modules), batched across rows: one catalog read per data source, one $in query per directory object, the metadata store for positions. Unresolvable values draw muted with the shared unresolved mark. ObjectGrid passes the widget hint and the row on column paths A-D, the link cells and the inline record panel. Not done, with reasons in the PR: object icon (catalog carries no icon), the type chip before the recipient (the adjacent recipient_type column already shows it; a copy would be a second derivation), and access-level tokens (select cell already draws a chip; the matrix tones are private to PermissionMatrixEditor).",
"tests": "All on merged head f4334e9. New: packages/fields/src/tests/widgetRefCells-12613.test.tsx (10 cases, includes recipient sales_manager renders 'Sales Manager' and the object column renders 'Account' with title 'Account (crm_account)') and packages/plugin-grid/src/tests/widgetHintedColumns-12613.test.tsx (paths A-D with a plain-text control column).
pnpm exec vitest run --maxWorkers=2 packages/fields/ packages/plugin-grid/ packages/plugin-detail/-> 'Test Files 751 passed | 2 skipped (753)', 'Tests 8776 passed | 21 skipped (8797)', exit 0 (VERDICT command-exit 0, lock held 38m on a shared box). Ablation 1 on the committed fix via scripts/ablation-replace.mjs (anchor 1->0, blob 41d9c158a162->0e19916f6a39, promotion guard disabled): 'Tests 11 failed | 3 passed (14)', the 3 green being the unchanged-face cases as predicted; restore proven (blob == HEAD, git diff HEAD empty). Ablation 2 (path B type cell without dependentValues): only 'B' red, 'Tests 1 failed | 3 passed (4)'; restore proven. No build/dist leg: vitest aliases @object-ui/fields to src. Direction observed: turned red, as predicted.","gates": "Local (hand-derived for objectui; objectui has no dispatch-gates.mjs, so no --ran reconciliation):
turbo run build --filter='@object-ui/plugin-grid^...' --concurrency=2BUILD_EXIT=0 on merged tree;pnpm --filter @object-ui/fields type-check0;pnpm --filter @object-ui/plugin-grid type-check0;pnpm --filter @object-ui/plugin-detail type-check0 (fields tsconfig.test.json --listFiles counts the new test: 2 hits); eslint on the 13 changed source/test files with the repo config: exit 0, 0 errors (with --no-inline-config: 1 error at an untouched ObjectGrid line whose react-hooks/static-components disable comment that flag ignores, not this diff);node scripts/check-changeset-presence.mjs0;pnpm check:new-line-citations0 ('0 new citation(s)');node scripts/check-governed-queue-guard.mjs --testNOT GOVERNED; control-byte grep on changed files: no match. i18n family not run: no locale file changed. CI on PR head f4334e9 at report time: label success; Test shards 1-16, Type Check, Lint, Build & E2E, Test (dist pins), Repo Gates, Changeset Declaration/Bump Policy/Fixed Group Check/Claim Re-read/Overwrite Report, Line Citation Gate, Governed Surface Queue Guard, Build Docs, Bundle Analysis, Live E2E (informational) all in_progress; coverage legs and dependabot skipped. NOT MEASURED: screenshot (1280 px), reason: no backend stack reachable (objectstack checkout has no node_modules or built packages); the seat runs the browser check at review.","mcp_calls": "0",
"api_writes": "2 relay strokes via scripts/pm/fleet-write/dispatch.mjs (each one POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create -> POST /repos/objectstack-ai/objectui/pulls (draft) + POST /repos//issues/12646/assignees; (2) comment -> POST /repos//issues/12613/comments (this report). git push of the branch (3 pushes) is not REST. No label writes.",
"files_changed": [
".changeset/12613-widget-ref-read-cells.md",
"packages/fields/README.md",
"packages/fields/src/index.tsx",
"packages/fields/src/widgets/ObjectRefField.tsx",
"packages/fields/src/widgets/RecipientPickerField.tsx",
"packages/fields/src/widgets/objectCatalog.ts",
"packages/fields/src/widgets/recipientKinds.ts",
"packages/fields/src/widgets/refLabelDirectory.ts",
"packages/fields/src/tests/widgetRefCells-12613.test.tsx",
"packages/fields/src/tests/cellRenderers.objectLiteral-8596.test.tsx",
"packages/plugin-grid/src/ObjectGrid.tsx",
"packages/plugin-grid/src/cellRendererResolution.ts",
"packages/plugin-grid/src/tests/widgetHintedColumns-12613.test.tsx",
"packages/plugin-detail/src/tests/summaryChip.badgeFitCensus-8464.test.tsx"
],
"line_budget": "n/a — no skills/** or governed ledger touched (governed-queue guard: NOT GOVERNED)",
"deviations": [
"File surface breached, both forced by the change and named in the PR body: packages/plugin-detail/src/tests/summaryChip.badgeFitCensus-8464.test.tsx (two census rows plus its fit-side count 36 -> 38; the census names every registered cell type and reds by name on a new one) and packages/fields/README.md (outside packages/fields/src; AGENTS.md #2 docs-driven).",
"Clause-② written as 'yes' at the PR body's line start, corrected from the claim's provisional 'no' per the claim's own instruction: built fields dist/index.d.ts gains CellRendererProps.dependentValues (optional) and a widget? member on resolveCellRendererType's object arm; plugin-grid dist/index.d.ts unchanged (0 references to cellRendererResolution). The claim comment itself still reads 'no'.",
"Branch history: fast-forwarded onto origin/main 619e3f7 (objectui#12633 landed) before the first edit, then one merge of origin/main 07b0632 before opening the PR; gates re-run on the merged tree. No conflict.",
"Two verify-lock acquisitions answered queue-timeout (exit 99, NOT MEASURED at the time); both measurements were re-taken later under the same slot and are the ones quoted.",
"The first pnpm install was started with a trailing & and its log read afterwards (it had finished, exit 0); every later heavy run was foreground or nohup + tail --pid.",
"The card's acceptance item
pnpm --filter @object-ui/app-shell testwas not run locally: app-shell is not touched; declared to CI's test shards."],
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed — DetailSection (plugin-detail) now promotes object_name to the object cell through resolveCellRendererType but passes no row, so a record page's recipient_id still reads as text; one-line follow-up (dependentValues={data}). Dedupe words: detail recipient label, dependentValues DetailSection, recipient-picker read cell.",
"carrier: none · noted, not filed — the By Object list view's group band prints the object api name (band labels are strings built by the grid's grouping, not cells); read from source, not measured in a browser. Dedupe words: group band object label, by_object grouping api name, sharing rule group header.",
"carrier: none · noted, not filed — @objectstack/spec's FieldSchema.widget describe() calls it a 'Form widget override' while list cells now read it for object-ref / recipient-picker; docs face only. Dedupe words: widget describe form override, widget read cell, FieldSchema widget doc."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 16, 2026
Claim: PM loop round 1 (re-stated by the same session on the same branch to correct the
Clause-②line of claim6210498989and to add the two files the dev's report names; nothing else changes)Session:
session_01Vk15bz8xHtKGUfXfCm16h4Account:
os-elon-musk(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-12613-sharing-rule-labelsWorktree:
objectui-issue-12613Domain:
domain:uiSeat:
domain:ui#3File surface: as claim
6210498989declared, plus the two files report6212592185names outside it:packages/plugin-detail/src/__tests__/summaryChip.badgeFitCensus-8464.test.tsx(the census names every registered cell type) andpackages/fields/README.md(objectui AGENTS.md #2, docs-driven) (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: opus(default tier;dispatch-gates --tier --repo objectstack-ai/objectuiover the named paths: no path-derived mandate)Clause-②: yes
Clause-② reason: the PR widens a published surface of
@object-ui/fields, whose only entry isexports["."]→dist/index.d.ts.CellRendererPropsgains the optionaldependentValues?: Record<string, unknown>, andresolveCellRendererType's object argument gainswidget?: string. Both were measured by the dev on the builtdist/index.d.tsand re-read by the seat in the PR diff at headf4334e9. No@object-ui/typesmember and no authoring key change. The seat'sTIER_DEFAULTreview of record comes before ready.Responsibility: n/a — not a defect card
Thread-read: 6212592185
Serial constraints cleared: PR objectui#12633 (objectui#12620) has landed, and this branch merged
mainafter it. PR objectui#12641 (objectui#12622, seat 2) is open onpackages/fields/src/index.tsx, the same file as this PR's cell resolution, in its date widget registration; whichever lands later mergesmainfirst and keeps both intents. No other open objectui PR touchespackages/fields/src/orpackages/plugin-grid/src/(all open PRs' file lists, read 2026-10-16T09:38Z).Generated by Claude Code
objectstack-fleet commentedon Oct 16, 2026
os-dev-report
{
"issue": 12613,
"status": "done",
"branch": "claude/issue-12613-sharing-rule-labels",
"pr": "#12646",
"session": "session_01Vk15bz8xHtKGUfXfCm16h4",
"premise_still_valid": true,
"round": "patch round 1 (Bundle Analysis red on f4334e9)",
"summary": "I reproduced the failure locally. On the merged tree with this branch's directory, the ui-components chunk measured 289,748 gzip bytes against its 289,000 ceiling, over by 748. The PM hypothesis was refuted: the old directory did not get in through the lazy edit widgets. Every packages/fields module lands in the eager ui-components chunk whatever imports it, because the ui-components advancedChunks group captures by path; scripts/vite-ineffective-dynamic-imports.ts documents this. So no lazy import inside fields can move bytes out. A control build of main's fields src (merge-base 8ed7e5a, restore proven) measured 288,585, which leaves 415 bytes of headroom on main. Fix at the cause, following the RECORD_POINTER_CARD_TYPE arrangement. @object-ui/fields keeps only the rule: the widget promotion, the two keys registered with TextCellRenderer as their default face, and the optional CellRendererProps.dependentValues. It also publishes the edit widgets' reads and kind table (loadObjectCatalog, objectRefOptionLabel, RECIPIENT_RECORD_KINDS, RECIPIENT_REGISTRY_KINDS, FIELD_RECIPIENT_TYPE, loadRecipientCandidates, recipientCandidateLabel) and UnresolvedReferenceMark. @object-ui/app-shell registers the label faces under the two keys. The faces and their label directory load lazily, on the first cell that draws one, and land in a lazy chunk (WidgetRefCellFaces-.js) outside the eager closure. The directory also became one catalog mechanism: record kinds read the picker's own candidate query, now shared with the picker as loadRecipientCandidates, instead of a batched $in reader. No ceiling, budget file, allow-list or chunk group was touched.",
"tests": "All on merged head a2ed222, after merging origin/main twice this round (fb38bcd, then a2ed222). Targeted run:
pnpm exec vitest run --maxWorkers=2over 21 files (fields widgetRefCells-12613; app-shell WidgetRefCellFaces-12613; plugin-grid widgetHintedColumns-12613; the five fields cell censuses and listCellRendererTypes-8734; plugin-detail summaryChip.badgeFitCensus-8464; grid linkCellNoNestedAnchor-11817, formatHintedColumnRenderer-8920, cellRendererResolutionBoundary-8920, gridDependentValues-7165; RecipientPickerField.test and its fieldRecipient-7613; widget-aria-invalid / widget-dom-leak e2e; booleanField.readonlyNonBoolean-8593; app-shell product-neutral-renderer.ratchet, record-pointer-card-renderer, decisionOutputParams) -> 'Test Files 21 passed (21)', 'Tests 572 passed (572)', exit 0. Acceptance cases now live in app-shell: 'a rule with recipientsales_managerrenders "Sales Manager", and its object renders "Account"' (through the real keys a declared widget resolves to; tooltip 'Account (crm_account)'). The grid test now uses probe faces to pin the grid's half of the contract: the widget key and the row (recipient_type) on paths A-D. Ablations on the committed rework via scripts/ablation-replace.mjs: A1, promotion guard disabled (anchor 1->0, blob cef8608ee3b2 -> 3d22c765db13): 'Tests 7 failed | 11 passed (18)', red in all three files as predicted (the default-text-face case stays green by design). A2, the recipient-picker registration deleted (blob 685e8575e11f -> 047eea89aba3): 'Tests 2 failed | 8 passed (10)'. Both restores proven (blob == HEAD, git diff HEAD empty). Not re-run: the full fields/plugin-grid/plugin-detail suites (last full run, round 0 on f4334e9: 751 files / 8776 tests green). This round's fields change returns the two keys to the text face main draws, and the reach was covered by the targeted set.","gates": "Reproduction (merged tree fb38bcd; fields dist built from the committed directory, timestamp before any edit; console aliases fields to src):
turbo run build --filter='./packages/*'+pnpm --filter @object-ui/console build+node scripts/check-eager-closure-budget.mjs-> exit 1, 'ui-components 283.0 KB / 282.2 KB ceiling (OVER by 0.7 KB)' = 289,748 gzip bytes. Control (main's fields src at 8ed7e5a, console rebuilt, restored and proven): ui-components 288,585. After the fix, on final head a2ed222: PKGS_BUILD_EXIT=0; fields / app-shell / plugin-grid type-check TC_EXIT=0 each; CONSOLE_BUILD_EXIT=0; check-eager-closure-budget BUDGET_EXIT=0 ('Console eager closure is 3096.6 KB gzipped across 70 of 2253 chunks (budget: 3119.4 KB, headroom: 22.8 KB)', ui-components 288,954 / 289,000 gz, '282.2 KB / 282.2 KB ceiling (headroom 0.0 KB)'; chunk membership pass, fields 88 modules in ui-components);pnpm check:sdui-registration-pinsexit 0;node scripts/check-side-effects-array.mjs0 (app-shell sideEffects names the new registration module);node scripts/check-phantom-dependencies.mjs0; eslint on the 16 changed .ts/.tsx files exit 0, 0 errors;node scripts/check-changeset-presence.mjs0;pnpm check:new-line-citations0 new; governed-queue guard NOT GOVERNED; control-byte grep: no match. On the earlier head 0458f1a (before the second merge) ui-components measured 288,744; the second main merge (objectui#12622's date picker among it) moved it +210. CI on PR head a2ed222 at report time: 0 check-runs registered yet (read right after the push); mergeable_state 'blocked', draft.","mcp_calls": "0",
"api_writes": "This round: 1 relay stroke via scripts/pm/fleet-write/dispatch.mjs (POST /repos/objectstack-ai/objectstack/dispatches) -> comment POST /repos//issues/12613/comments (this report). git push of the branch (1 push, a2ed222) is not REST. PR body not edited; no label writes. Round 0 writes as reported earlier (pr_create + assignees, report comment).",
"files_changed": [
".changeset/12613-widget-ref-read-cells.md",
"packages/app-shell/package.json",
"packages/app-shell/src/index.ts",
"packages/app-shell/src/views/widget-ref-cells/WidgetRefCellFaces.tsx",
"packages/app-shell/src/views/widget-ref-cells/tests/WidgetRefCellFaces-12613.test.tsx",
"packages/app-shell/src/views/widget-ref-cells/refLabelDirectory.ts",
"packages/app-shell/src/views/widget-ref-cells/widget-ref-cell-renderers.tsx",
"packages/fields/README.md",
"packages/fields/src/tests/cellRenderers.objectLiteral-8596.test.tsx",
"packages/fields/src/tests/widgetRefCells-12613.test.tsx",
"packages/fields/src/index.tsx",
"packages/fields/src/widgets/ObjectRefField.tsx",
"packages/fields/src/widgets/RecipientPickerField.tsx",
"packages/fields/src/widgets/objectCatalog.ts",
"packages/fields/src/widgets/recipientKinds.ts",
"packages/plugin-detail/src/tests/summaryChip.badgeFitCensus-8464.test.tsx",
"packages/plugin-grid/src/ObjectGrid.tsx",
"packages/plugin-grid/src/tests/widgetHintedColumns-12613.test.tsx",
"packages/plugin-grid/src/cellRendererResolution.ts"
],
"line_budget": "n/a — no skills/** or governed ledger touched (governed-queue guard: NOT GOVERNED)",
"deviations": [
"File surface widened to @object-ui/app-shell (packages/app-shell/src/views/widget-ref-cells/**, src/index.ts, package.json sideEffects), on the dispatch's own clause: when measurement shows the fix belongs in another package, fix it there and give the reason. The measured reason: fields cannot defer anything, because every fields module is group-captured into the eager ui-components chunk. The host-face arrangement is the existing RECORD_POINTER_CARD_TYPE precedent. app-shell's package.json sideEffects is a published-contract field, and the changeset now also lists '@object-ui/app-shell': minor.",
"Public surface of @object-ui/fields grew beyond round 0, measured on the built dist/index.d.ts (1 hit each): loadObjectCatalog, objectRefOptionLabel, RECIPIENT_RECORD_KINDS, RECIPIENT_REGISTRY_KINDS, FIELD_RECIPIENT_TYPE, loadRecipientCandidates, recipientCandidateLabel, UnresolvedReferenceMark (plus their types). CellRendererProps.dependentValues is unchanged from round 0. app-shell's dist/index.d.ts gains only the side-effect import, no export. The seat corrects the claim's Clause-② line.",
"Behaviour change versus round 0, for the PR body: the recipient record kinds are now named from the picker's candidate query (at most 500 rows ordered by name, read once per data source per kind), not a batched $in query. A record past the picker's first 500 reads muted-unresolved, exactly as the open picker does not offer it.",
"Two lock acquisitions in this round timed out (exit 99) before running; both were resumed on slot 12613 and measured. One reproduction build overlapped my edits. Its fields dist predates them (timestamp 09:54:48 against edits from 09:59). But its console build aliases fields to src, so its ui-components reading (289,748) may already include the first slimmer directory. The main control build (288,585) and the final readings are clean.",
"origin/main was merged twice this round: once at the start as the seat asked, and once more before the push, because objectui#12622 (fields date widgets) had landed and could only move the budgeted chunk. The final readings are all on that merged head."
],
"pr_body_changes": "The body is not edited here. For the seat: (1) 'What changed / @object-ui/fields' now describes fields as owning the promotion, the two keys with a text default face, CellRendererProps.dependentValues and the published reads/kind table plus UnresolvedReferenceMark. The object/recipient faces, the label directory and its once-per-data-source catalogs move to a new '@object-ui/app-shell' bullet: registered under the two keys, loaded lazily into WidgetRefCellFaces-.js outside the eager closure. (2) Replace 'record kinds collected for one microtask and read with one $in query per directory object' with 'record kinds named from the picker's own candidate query, read once per data source'. (3) Tests section: the acceptance cases are in packages/app-shell/src/views/widget-ref-cells/tests/WidgetRefCellFaces-12613.test.tsx; the grid test pins key + row with probe faces; the round-1 ablation figures are in this report. (4) Add a Bundle line: ui-components 288,954 / 289,000 gz on a2ed222; the main control at 8ed7e5a is 288,585. (5) Surface notes: add app-shell.",
"targets_1_2_answers": [
"Target 1 (object icon): no. The board's objectDef is the LISTED object's definition (sys_sharing_rule, icon 'shield-check'), so objectDef.icon names the wrong object. The referenced object's icon is in the console metadata store's object list, the same GET /meta/object the catalog reads, which carries full definitions with icon. The app-shell face could read it there without a second derivation. Not built.",
"Target 2 (type chip): yes, the board holds it. The grid already has objectDef.fields.recipient_type.options and translates them (translateOptions) for that column, so the chip's text exists without a second derivation. It does not reach the recipient face today, because a cell receives only its own field meta plus the row's raw values. The grid would have to hand over the sibling's option label. Not built."
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed. On merged head a2ed222, main plus this branch leaves the ui-components chunk 46 gzip bytes under its 289,000 ceiling (main alone: 288,585 at 8ed7e5a, plus about +210 from the latest main merge). The next fields-side change of any size will trip it. Ceiling capacity is the maintainer's to rule on; nothing was raised. Dedupe words: ui-components ceiling headroom, eager closure per-chunk budget fields, check-eager-closure-budget ui-components.",
"carrier: none · noted, not filed. DetailSection (plugin-detail) resolves object_name to the object-ref key and now draws the app-shell face there, but it passes no row, so a record page's recipient_id reads as text. Fix: dependentValues={data}. Dedupe words: detail recipient label, dependentValues DetailSection.",
"carrier: none · noted, not filed. The By Object view's group band prints the object api name (band labels are strings built by the grid's grouping); read from source, not measured. Dedupe words: group band object label, by_object grouping api name.",
"carrier: none · noted, not filed. @objectstack/spec FieldSchema.widget describe() says 'Form widget override', while list cells now read it for these two hints; docs face only. Dedupe words: widget describe form override, FieldSchema widget doc."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 16, 2026
Claim: PM loop round 2 (re-stated by the same session on the same branch: the file surface moves the label faces into
@object-ui/app-shell, on the dev's measurement in report6214194030; theClause-②reason is updated)Session:
session_01Vk15bz8xHtKGUfXfCm16h4Account:
os-elon-musk(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-12613-sharing-rule-labelsWorktree:
objectui-issue-12613Domain:
domain:uiSeat:
domain:ui#3File surface: as claim
6212639515declared, pluspackages/app-shell/src/views/widget-ref-cells/**(the lazily loaded object and recipient read faces and their label directory, registered under the two@object-ui/fieldskeys on theRECORD_POINTER_CARD_TYPEpattern),packages/app-shell/src/index.ts(the side-effect registration import) andpackages/app-shell/package.json(sideEffects). The measured reason: everypackages/fieldsmodule is captured by path into the eagerui-componentschunk, so no face there can be deferred (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: opus(default tier;dispatch-gates --tier --repo objectstack-ai/objectuiover the named paths: no path-derived mandate)Clause-②: yes
Clause-② reason:
@object-ui/fields(entryexports["."]) gains the optionalCellRendererProps.dependentValues,widget?onresolveCellRendererType's argument, and the reads and kind table the app-shell face imports. Patch round 2 cuts these to what app-shell actually imports, and the final names are read on the builtdist/index.d.tsin the report.@object-ui/app-shellmoves a published contract field (sideEffects) and exports nothing new. The seat'sTIER_DEFAULTreview of record comes before ready.Responsibility: n/a — not a defect card
Thread-read: 6214194030
Serial constraints cleared:
packages/app-shell/src/views/widget-ref-cells/**is new;app-shell/src/index.tsandpackage.jsonare named by no other in-flight lane claim or open PR (read 2026-10-16T11:11Z). PR objectui#12641 (packages/fields/src/index.tsx) has landed, and this branch merged it.Generated by Claude Code
objectstack-fleet commentedon Oct 16, 2026
Correction to claim
6214243680,Serial constraints cleared:line (same session, 2026-10-16T11:12Z).packages/app-shell/package.jsonIS also open in PR objectui#12648 (objectui#12626, seat 4, the18.0.0-next.2bump). That PR changes the@objectstack/specdependency range; this card changessideEffects, a different field. Whichever lands later mergesmainfirst. The rest of the claim stands.Generated by Claude Code
objectstack-fleet commentedon Oct 16, 2026
os-dev-report
{
"issue": 12613,
"status": "done",
"branch": "claude/issue-12613-sharing-rule-labels",
"pr": "#12646",
"session": "session_01Vk15bz8xHtKGUfXfCm16h4",
"premise_still_valid": true,
"round": "patch round 2 (head d47fb07)",
"summary": "(1) Record recipients resolve by exact id again, inside the lazy app-shell directory. Every user / team / business-unit id any cell asks for in one tick goes out as one
$inquery per kind (chunked at 100). The directory caches the row, and the face builds the label at render with withoutDeniedFields(row, usePermissions(), object, [storeField]), so the fields the viewer may not read are dropped as the lookup picker drops them, and a policy that loads later relabels the cell. A recipient past the picker's first 500 candidates is named. (2) The object face reads the console metadata store's object list,ensureType('object'): the sameGET /meta/objectlist the picker offers, as full definitions. Label and icon come from that one session-cached read, with no request of its own. The icon is drawn the way the console's navigation draws an object's: LazyIcon, size-3.5, muted, with the Database glyph as fallback. (3) @object-ui/fields publishes only what app-shell imports. loadObjectCatalog and loadRecipientCandidates are gone, and the widgets keep their own reads inline exactly as on main; objectCatalog.ts became objectRefLabel.ts, holding only the sharedLabel (name)spelling. The fields diff against main is now the promotion, the two text-default keys, the optional dependentValues prop, the kind table moved into recipientKinds.ts, and six published names.","tests": "On head d47fb07 (main 8614705 merged; origin/main has since moved, see deviations). Targeted run:
pnpm exec vitest run --maxWorkers=2over the same 21 files as round 1 -> 'Test Files 21 passed (21)', 'Tests 572 passed (572)', exit 0. The app-shell face file, verbose: 11 passed. They include 'names record recipients by EXACT id — one past the first 500 candidates too — with one$inquery per kind', which pins: a 600-user directory whose candidate query returns only the first 500; usr_550 named 'User 550'; exactly 2 find calls (sys_user $in [usr_550, usr_7, usr_gone] with $top 3; sys_team $in [team_eu]); usr_gone muted. Also 'leaves the fields the viewer may not read out of a record label, as the lookup picker does': PermissionProvider denies sys_user.name, so the label reads 'user550@example.com' and not 'User 550'. And 'draws the object's icon and label …': svg.lucide-building for crm_account (icon 'building'), svg.lucide-database for crm_contact (no icon), one ensureType('object') call. Ablations via scripts/ablation-replace.mjs on the committed head, each restore proven (blob == HEAD, git diff HEAD empty). A3, the record read put back on the candidate query ($top: 500, $orderby: name, blob 83e934f1fcf1 -> 3a9ae8d66290): 'Tests 2 failed | 9 passed (11)'. Both reds are expected: the exact-id case, and the policy case, whose usr_550 is also off the candidate page. A4, icon dropped (name={undefined}): 'Tests 1 failed | 10 passed (11)', the icon case. A5, withoutDeniedFields removed: 'Tests 1 failed | 10 passed (11)', the policy case. Observed direction: turned red, as predicted.","gates": "On head d47fb07:
turbo run build --filter='./packages/*' --concurrency=2PKGS_BUILD_EXIT=0; fields / app-shell / plugin-grid type-check TC_EXIT=0 each;pnpm --filter @object-ui/console buildCONSOLE_BUILD_EXIT=0;node scripts/check-eager-closure-budget.mjsBUDGET_EXIT=0. Its reading: 'Console eager closure is 3096.6 KB gzipped across 70 of 2253 chunks (budget: 3119.4 KB, headroom: 22.8 KB)', and ui-components at 288,905 / 289,000 gzip bytes ('282.1 KB / 282.2 KB ceiling (headroom 0.1 KB)'), which is −49 against round 1's a2ed222 reading of 288,954. The faces chunk WidgetRefCellFaces-.js (4,572 raw bytes) is not in the eager closure.pnpm check:sdui-registration-pinsexit 0;node scripts/check-side-effects-array.mjs0;node scripts/check-phantom-dependencies.mjs0; eslint on the changed .ts/.tsx files exit 0 with 0 errors;node scripts/check-changeset-presence.mjs0;pnpm check:new-line-citations0 new; governed-queue guard NOT GOVERNED; control-byte grep: no match. No ceiling, budget file, allow-list or chunk group was touched. CI on d47fb07: not read yet at the time of this comment; the final message carries the reading.","published_surface": "Measured on the built packages/fields/dist/index.d.ts. Six new exported names, 1 hit each: UnresolvedReferenceMark (
export declare function), objectRefOptionLabel (re-exported from ./widgets/objectRefLabel.js), and RECIPIENT_RECORD_KINDS, RECIPIENT_REGISTRY_KINDS, FIELD_RECIPIENT_TYPE and recipientCandidateLabel (re-exported from ./widgets/recipientKinds.js). Two widened existing members: CellRendererProps.dependentValues (optional, a string-keyed Record of unknown) and thewidget?: stringmember on resolveCellRendererType's object arm. No longer exported (0 hits): loadObjectCatalog, loadRecipientCandidates, ObjectHeader, ObjectCatalogSource, RecipientMapping, RegistryRecipientMapping; the two mapping interfaces are named only inside recipientKinds.d.ts. @object-ui/app-shell's dist/index.d.ts gains only the side-effect import, no export. @object-ui/plugin-grid's published closure is unchanged.","mcp_calls": "0",
"api_writes": "This round: 1 relay stroke via scripts/pm/fleet-write/dispatch.mjs (POST /repos/objectstack-ai/objectstack/dispatches) -> comment POST /repos//issues/12613/comments (this report). git push of the branch (1 push, d47fb07) is not REST. PR body not edited; no label writes.",
"files_changed": [
".changeset/12613-widget-ref-read-cells.md",
"packages/app-shell/package.json",
"packages/app-shell/src/index.ts",
"packages/app-shell/src/views/widget-ref-cells/WidgetRefCellFaces.tsx",
"packages/app-shell/src/views/widget-ref-cells/tests/WidgetRefCellFaces-12613.test.tsx",
"packages/app-shell/src/views/widget-ref-cells/refLabelDirectory.ts",
"packages/app-shell/src/views/widget-ref-cells/widget-ref-cell-renderers.tsx",
"packages/fields/README.md",
"packages/fields/src/tests/cellRenderers.objectLiteral-8596.test.tsx",
"packages/fields/src/tests/widgetRefCells-12613.test.tsx",
"packages/fields/src/index.tsx",
"packages/fields/src/widgets/ObjectRefField.tsx",
"packages/fields/src/widgets/RecipientPickerField.tsx",
"packages/fields/src/widgets/objectRefLabel.ts",
"packages/fields/src/widgets/recipientKinds.ts",
"packages/plugin-detail/src/tests/summaryChip.badgeFitCensus-8464.test.tsx",
"packages/plugin-grid/src/ObjectGrid.tsx",
"packages/plugin-grid/src/tests/widgetHintedColumns-12613.test.tsx",
"packages/plugin-grid/src/cellRendererResolution.ts"
],
"line_budget": "n/a — no skills/** or governed ledger touched (governed-queue guard: NOT GOVERNED)",
"deviations": [
"origin/main was merged at the start of the round; it was already at 8614705, so the merge was a no-op. Before the push, main had moved to a5bb7ac (objectui#12626 deps v18 bump, objectui#12605 app-shell). Those commits touch neither packages/fields nor packages/components, and
git merge-tree --write-tree HEAD origin/mainreports no conflict (exit 0). Under the merge-only-for-conflict-or-main-red rule I did not merge again. The branch reads one merge behind, and the merge queue will rebuild it on main.","The object face no longer reads the picker's getObjects catalog: it reads the console metadata store, which serves the same /meta/object list and carries the icon. Outside a MetadataProvider the face draws the stored text, as the registry recipient kinds always have.",
"The object-icon tests rely on LazyIcon's lucide DynamicIcon resolving inside vitest (svg.lucide-building / svg.lucide-database found under waitFor). That is the same mechanism the console's navigation uses, so the pin reads the real glyph, not a mock."
],
"accepted_deviations_for_the_seat": [
"Target 2, the type chip before the recipient: not built. Every list view that shows recipient_id also shows the recipient_type column beside it, and the chip's text is that sibling select's translated option label. A cell receives only its own field meta plus the row's raw values, so drawing the chip would need the grid to hand over the sibling's option label.",
"Target 3, access-level tokens: not built. access_level already draws as a chip through SelectCellRenderer from its option labels. The platform declares no option colour, and the permission matrix's tones are private functions in PermissionMatrixEditor (app-shell), so a generic cell is not coupled to them."
],
"pr_body_lines_for_seat": [
"Line 2:
Clause-②: yes— the published fields closure gains six names (UnresolvedReferenceMark, objectRefOptionLabel, RECIPIENT_RECORD_KINDS, RECIPIENT_REGISTRY_KINDS, FIELD_RECIPIENT_TYPE, recipientCandidateLabel) and widens two members (CellRendererProps.dependentValues, resolveCellRendererType's widget arm), measured on dist/index.d.ts. app-shell and plugin-grid published closures unchanged.","What changed / @object-ui/fields: the widget promotion;
object-refandrecipient-pickerregistered with the stored text as their default face (the RECORD_POINTER_CARD_TYPE arrangement); the optional CellRendererProps.dependentValues; the kind table moved into recipientKinds.ts and published with recipientCandidateLabel, objectRefOptionLabel and UnresolvedReferenceMark for the host face. The edit widgets list and store exactly what they did.","What changed / @object-ui/app-shell (new): registers the two faces under those keys and loads them, with their label directory, lazily into WidgetRefCellFaces-.js, outside the eager closure. The object face draws the icon and label from the console metadata store's object list (
ensureType('object')). The recipient face names record kinds by exact id, one$inquery per kind per tick with denied fields removed (withoutDeniedFields); positions come from the metadata store and thefieldkind from the shared object's schema.","What changed / @object-ui/plugin-grid: unchanged from round 0. The widget hint and the row are passed on paths A-D, the link cells and the inline record panel.",
"Why app-shell: every packages/fields module rides the eager ui-components chunk whatever imports it (scripts/vite-ineffective-dynamic-imports.ts), and that chunk's per-chunk ceiling refused the label reader (289,748 / 289,000 measured on round 0's head). Main alone sat at 288,585 at 8ed7e5a.",
"Bundle: ui-components 288,905 / 289,000 gzip on d47fb07; eager closure 3096.6 KB of 3119.4 KB; the faces chunk is lazy.",
"Tests: the acceptance cases live in packages/app-shell/src/views/widget-ref-cells/tests/WidgetRefCellFaces-12613.test.tsx ('sales_manager' renders 'Sales Manager'; the object renders its icon and 'Account' with tooltip 'Account (crm_account)'; a recipient past the first 500 candidates is named by exact id; a denied name field is left out of the label). packages/plugin-grid/src/tests/widgetHintedColumns-12613.test.tsx pins key + row on paths A-D with probe faces. packages/fields/src/tests/widgetRefCells-12613.test.tsx pins the promotion, the default faces and the published spellings.",
"Card targets: 1 done (icon + label + api-name tooltip); 2 type chip and 3 access-level tokens are accepted deviations, with the reasons above.",
"Surface notes: app-shell added (widget-ref-cells/**, src/index.ts, package.json sideEffects); the plugin-detail census rows and the fields README as before.",
"Remove from the body: every mention of
loadObjectCatalog,loadRecipientCandidates, 'one$inquery per directory object' read by fields, and the round-0 statement that the fields cells draw the labels."],
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed. The ui-components chunk has 95 gzip bytes of headroom on d47fb07 (288,905 / 289,000; main alone was 288,585 at 8ed7e5a, before the latest main merges). The next fields-side change of any size will trip it. Ceiling capacity is the maintainer's to rule on; nothing was raised. Dedupe words: ui-components ceiling headroom, eager closure per-chunk budget fields.",
"carrier: none · noted, not filed. DetailSection (plugin-detail) resolves object_name to the object-ref key and draws the app-shell face, but passes no row, so a record page's recipient_id reads as text. Fix: dependentValues={data}. Dedupe words: detail recipient label, dependentValues DetailSection.",
"carrier: none · noted, not filed. The By Object view's group band prints the object api name (band labels are strings built by the grid's grouping); read from source, not measured. Dedupe words: group band object label, by_object grouping api name.",
"carrier: none · noted, not filed. @objectstack/spec FieldSchema.widget describe() says 'Form widget override', while list cells now read it for these two hints; docs face only. Dedupe words: widget describe form override, FieldSchema widget doc."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 16, 2026
os-dev-report
{
"issue": 12613,
"status": "done",
"branch": "claude/issue-12613-sharing-rule-labels",
"pr": "#12646",
"session": "session_01Vk15bz8xHtKGUfXfCm16h4",
"premise_still_valid": true,
"round": "merge round (new head f667f86)",
"summary": "I merged origin/main f78efdd into d47fb07 in a fresh worktree; the new head is f667f86, a merge commit with no rebase and no force-push. One file conflicted: packages/plugin-grid/src/ObjectGrid.tsx, against f78efdd (objectui#12608: list cells show the record a referenceVia pointer names). Its seven hunks are the same seven cell closures on both sides. Main prefixed each with
pointerFace?.(row) ??; this branch addeddependentValues={row}to the CellRenderer / LinkContentRenderer element. I resolved each hunk by keeping both: main'spointerFace?.(row) ??prefix, followed by the RENDERER element carrying this branch'sdependentValues={row}(the element's angle brackets are spelled out because the body sanitizer drops tag-shaped text). The resolved file has 0 conflict markers, 7pointerFace?.(row)(the same count as main) and 7dependentValues={row}(the same as the branch), and the record panel'sdependentValues={record}is intact. packages/fields/src/index.tsx (a RECORD_POINTER_CARD_TYPE doc paragraph) and packages/app-shell/package.json (@objectstack/spec ^18.0.0-next.2) auto-merged. No generated file or lockfile conflicted; pnpm install was re-run for the v18 dependency bump.","tests": "On merged head f667f86. Targeted run:
pnpm exec vitest run --maxWorkers=2over the same 21 files plus main's packages/plugin-grid/src/tests/ObjectGrid.recordPointerCell-12608.test.tsx, which pins the closures the conflict touched -> 'Test Files 22 passed (22)', 'Tests 575 passed (575)', exit 0. No ablation: this round merged and changed no tested behaviour.","gates": "On merged head f667f86:
turbo run build --filter='./packages/*' --concurrency=2PKGS_BUILD_EXIT=0; fields / app-shell / plugin-grid type-check TC_EXIT=0 each;pnpm --filter @object-ui/console buildCONSOLE_BUILD_EXIT=0;node scripts/check-eager-closure-budget.mjsBUDGET_EXIT=0. ui-components: 288,876 / 289,000 gzip bytes ('282.1 KB / 282.2 KB ceiling (headroom 0.1 KB)'), 124 bytes of headroom. Aggregate eager closure: 3,193,243 bytes, 'Console eager closure is 3118.4 KB gzipped across 70 of 2254 chunks (budget: 3119.4 KB, headroom: 1.0 KB)'; main's v18 bump moved vendor-objectstack to 1519.2 KB / 1519.2 KB (headroom 0.0 KB). framework 74.5 / 97.7 KB, i18n-locale-en 45.7 / 48.8 KB. Membership passes (fields 88 modules in ui-components, its src/widgets/date-picker subtree 2 in fields-date-picker). The faces chunk WidgetRefCellFaces-*.js is still outside the eager closure.pnpm check:sdui-registration-pins0;node scripts/check-side-effects-array.mjs0; eslint on the resolved ObjectGrid.tsx exit 0;node scripts/check-changeset-presence.mjs0;pnpm check:new-line-citations0. No ceiling touched. Two lock acquisitions timed out (exit 99; holder was slot 12082's verify1.sh, 20 minutes); the third, on the kept slot 12613, ran. CI on f667f86, read right after the push: 35 check-runs, 3 skipped (dependabot, two coverage legs), 31 in_progress, 1 queued, none completed yet. PR draft, mergeable_state 'behind', no longer 'dirty'.","merge_conflict": "packages/plugin-grid/src/ObjectGrid.tsx only, with main f78efdd (objectui#12608, list referenceVia pointer cells). Seven hunks: path A link+action, path A linked, path A type-only, path B primary link, path B type-only, path C, path D. Each was resolved as main's
pointerFace?.(row) ??prefix, followed by the RENDERER element withdependentValues={row}, keeping main's pointer face first and this branch's row hand-off on the fallback renderer.","mcp_calls": "0",
"api_writes": "This round: 1 relay stroke via scripts/pm/fleet-write/dispatch.mjs (POST /repos/objectstack-ai/objectstack/dispatches) -> comment POST /repos//issues/12613/comments (this report). git push of the branch (1 push, f667f86) is not REST. PR body not edited; no label writes.",
"files_changed_this_round": [
"packages/plugin-grid/src/ObjectGrid.tsx (conflict resolution in the merge commit)"
],
"line_budget": "n/a — no skills/** or governed ledger touched",
"deviations": [
"After the push, origin/main moved again to dcd1041 (three commits: objectui#12657 top bar, objectui#12661 plugin-detail, objectui#12659 Studio).
git merge-tree --write-tree HEAD origin/mainreports no conflict (exit 0), and in the budgeted packages they touch only one test file (packages/components/src/tests/iconOnlyButtonCensus-12143.test.ts, one deletion). Under the merge-only-for-conflict rule I did not merge again; the PR reads 'behind', not 'dirty'."],
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed. On f667f86 the eager closure has 1.0 KB of aggregate headroom (3118.4 of 3119.4 KB), vendor-objectstack has 0.0 KB and ui-components 124 bytes. Main's v18 dependency bump consumed most of the aggregate. The next page-load change of any size in any eager chunk may trip the aggregate. Ceiling capacity is the maintainer's to rule on; nothing was raised. Dedupe words: eager closure aggregate headroom, vendor-objectstack ceiling v18, ui-components headroom."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 16, 2026
State:
pm:dispatched→pm:blockedon objectui#12649 · ACCEPT done, the landing waits on an informational lane ·domain:uiseat 3 ·session_01Vk15bz8xHtKGUfXfCm16h4· 2026-10-16T13:44ZACCEPT → PR #12646 comment 6216821830
f667f86(merge round, report6216423846). Contract review of record6216766786(TIER_DEFAULT, declaration face, PASS). The seat's browser check (ond47fb07) names every recipient kind and every object, and draws each missing target raw, muted and marked.Live E2E (informational)is red on the head, as on every PR built onmainf62b6ff. Its anchor is objectui#12649 (domain:devx, p1). The seat's queue-entry rule reads every check, so the PR stays draft. This is the "work done, PR held by an external gate defect" use of this state. The claim, the assignee and the branch stay.ui-components124 bytes). Ifmaingrows the closure before the anchor lands, the merge onto it may tripBundle Analysis. objectui#12653 (in flight) carries the ceiling change.mainthrough the dev if the PR is conflicted or the budget moved, and lands the PR through the relay.Blocked-by: #12649
Unlock-action: re-check PR #12646
Generated by Claude Code
objectstack-fleet commentedon Oct 16, 2026
State:
pm:blocked→pm:dispatched· unlocked, in its landing window ·domain:uiseat 3 ·session_01Vk15bz8xHtKGUfXfCm16h4· 2026-10-16T14:26Zmainasd66ceea("the backend cold install writes overrides.vitest"). PRs whose heads were built after that commit readLive E2E (informational)green, measured on objectui#12699, refactor(react,plugin-form,console): read visibleWhen only — spec 18 retires the form view's visibleOn and the page component's visibility #12692, feat(app-shell,i18n): Home's Recently accessed lists records and list views with an object · app · time trail, beside the activity feed (objectui#12604) #12687 and fix(types): drop the page view-data check objectstack main deleted (objectui#12666) #12678.maingained a@object-ui/fieldschange (c6b7d82, the platform date picker in action dialogs), and this PR had 124 gzip bytes ofui-componentsheadroom.Bundle Analysison the new head decides it; if it reds, a dev round follows.main: the relay oppr_update_branchwithexpected_head_shaf667f86cc04e5f7d6c05c93f5cdf9d92bd511200, run objectstack-ai/objectstack 38915378828. That is GitHub's own merge; no code moved. PR feat(fields,plugin-grid): a declared object-ref / recipient-picker widget names what the value references in every list (objectui#12613) #12646's head is now1e8e8e2cb6a9e568777cbc8a1d2de591acd4a3df, a merge ofmainf8267df.1e8e8e2is green or an expected skip, the seat lands PR feat(fields,plugin-grid): a declared object-ref / recipient-picker widget names what the value references in every list (objectui#12613) #12646 through the relay (pr_ready+automerge_enable). ACCEPT stands; the merge moved no line of this PR's own diff.Generated by Claude Code
objectstack-fleet commentedon Oct 16, 2026
Landed ·
domain:uiseat 3 ·session_01Vk15bz8xHtKGUfXfCm16h4· 2026-10-16T14:49Z278bc29onmain(2026-10-16T14:48Z). Head1e8e8e2: ACCEPT6216821830, contract review6216766786(TIER_DEFAULT, PASS), the seat'smainsync6217655375.mainat278bc29:packages/fields/src/index.tsxcarriesWIDGET_CELL_RENDERER_TYPES;packages/app-shell/src/views/widget-ref-cells/widget-ref-cell-renderers.tsxregisters therecipient-pickerface.completedon the PR'sFixes. The seat stripspm:dispatchedin the same stroke as this note.recipient_id(DetailSectionpasses no row);Generated by Claude Code