Skip to content

fix(plugin-approvals): the approval request page's highlights strip draws no Object chip; the Details tab's record card names the target object by its label - #24500

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-24375-approval-object-chip
Oct 19, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-24375-approval-object-chip

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #24375

Clause-②: no

The served sys_approval_request_detail record page's highlights strip no longer draws an Object chip. It listed object_name, the target object's API name as stored, so a request about an announcement read showcase_announcement there. The page still names the target object, by its label: the Details tab's card for the record_id / object_name pointer pair heads the target record with its object's label. object_name leaves the object's highlightFields with the strip, so the Details tab does not draw it as a row.

Responsibility: plugin-approvals' own declarations (the page's highlights fields and the object's highlightFields) | the platform path that already names the target by its label is the console's pointer-pair card in the Details tab, fed by the declared referenceVia pair | reached by every user who opens an approval request's served page, live today.

Readings are against objectstack fb9c22aa0 (this branch's base; origin/main ce2e08ee3 is merged in at 5f21f062f and touches nothing under packages/plugins/plugin-approvals) and objectui at the console pin .objectui-sha = 0707b4ae37, by git show and git grep. The merged main moves the pin to 4016370af3c9; between the two pins the files on the chain below differ only in two hunks that retire the permission-facet-link widget (DetailSection.tsx, fields/src/index.tsx), not the pointer pair. The page was not driven in a browser; the section below says why and what stands in for it.

The measurement the route turns on: what the Details tab's card draws

The card's two routes split on one question: does the Details tab's record card already name the target object by its label? Statically, at the pin, it does, in the state where the reader can read the target.

  • The Details tab is a bare record:details node. objectui's record-details.tsx draws the object's highlightFields minus every field the mounted strip registers and minus the H1 field (objectui#12433's declared fallback). On main that is record_id alone.
  • record_id declares referenceVia: 'object_name'. DetailSection.tsx resolves the pair with resolveRecordPointer and draws it with the renderer registered under RECORD_POINTER_CARD_TYPE, which is app-shell's record-pointer-card-renderer.tsx, mounting RecordPreviewCard.
  • RecordPreviewCard, readable state: it reads the target object's definition (useMetadataItem('object', objectName)) and heads the card with objectLabel({ name, label: def.label }) from useObjectLabel, which resolves the object's translated label (objects.NAME.label in the reader's locale) and falls back to the definition's label. The record's title sits under it. objectui pins this in RecordPreviewCard.test.tsx ("heads it with the object label", getByText('Invoice')).
  • The other states name no object: unreadable (the reader may not read the target, or its definition is unavailable) draws "This record cannot be opened"; a request whose cancel_reason is record_deleted draws the tombstone, "Related record deleted"; loading draws a spinner.
  • The console reads the request through the approvals get (routeApprovalRecordReads; GET /approvals/requests/ID has no projection), so object_name stays in the row the card resolves whether or not any block lists it.

Why not a browser reading: there is no console build in this container (packages/console/dist is absent), so a screen reading needs objectui's console built at the pin plus the showcase closure. The static chain above and objectui's own pin on the card's heading stand in for it.

The route: no Object chip

strip Details tab (highlightFields minus strip)
main process_label, object_name, status, current_step_label, submitter_id, updated_at record_id (the card)
strip only process_label, status, … object_name, record_id: an "Object: showcase_announcement" row appears in the tab
this PR (strip and highlightFields) process_label, status, current_step_label, submitter_id, updated_at record_id (the card), unchanged

The page's own rule decides highlightFields: "The strip and the object's list change together, never the strip alone." The four list views' columns are not that list. The rule binds the strip to highlightFields because the Details tab reads highlightFields; no block on this page reads a list view's columns, so they stay as they are (see Acceptance notes).

Four axes

  • Real business need. The reader is an approver on the request page. Measured: the same page's Details tab already heads the target record with its object's label, translated. No producer writes an object-label column today, and no reader other than this chip would read one. The approvals get's object_label already serves the label to the inbox, live.
  • Long-term soundness. No new column, writer, backfill or snapshot. The label route would store an object_label snapshot while the get already serves a LIVE object_label under that name (enrichRows sets it from the registered schema on every read): the approvals get would answer the live label and the generic data door the stored one, two answers for one field. A second name for the same fact would be a second spelling instead.
  • Making AI mistakes harder. The page lists only declared fields the get serves, and the pin now holds the strip and the object's list free of the three machine-name fields of this family (process_name, current_step, object_name). A stored snapshot beside a live label is a second source an author can read by mistake.
  • Startup focus. This route removes one entry from two lists (4 files, under 70 lines). The label route, measured against plugin-approvals: serve display labels for the approval lists — the step and flow labels, "Waiting on", the Action History record pointer and acted_as #23889's footprint for the same kind of column (b74434c58: object, writer, backfill, four generated bundles, two integration tests), adds a declared column, its writer in openNodeRequest, a backfill leg in display-label-backfill.ts, four bundle entries and their tests, for a fact the page already shows.

A relabelled object

The card reads the object's definition when the page draws, so a relabelled object shows its new label on every request, old ones included. Under the label route a stored snapshot would keep the old label on requests opened before the rename, while the approvals get's live object_label would show the new one.

What this route gives up

Where the card cannot read the target (the reader may not read it, or the platform deleted it), the page names no object at all. Today the strip names it there by its API name. The card allows this ("The strip names the target object by its label, or does not name it at all").

Other readers of the object's highlightFields

PR #24279 listed them at its own pin; the halves this repository decides are re-checked here at 5f21f062f (the list views' columns, the two lookups' apiMethods, no page or related list naming sys_approval_request as its objectName, and the eight objects that declare a referenceVia pair):

  • ObjectView / ObjectGrid default columns: only for a list view that declares no columns; all four request list views declare them. Not reached.
  • InterfaceListPage, ObjectKanban and RelatedList defaults: no interface page, kanban or related list of requests is declared here. Not reached.
  • LookupField preview columns (deriveLookupColumns): the two lookups to a request (sys_approval_action.request_id, sys_approval_approver.request_id) sit on objects whose enable.apiMethods is ['get', 'list'], so no form opens that picker. Not reached.
  • RecordPreviewCard for a pointer pair that names a request row (an audit or activity row about a request): its strip loses the Object chip, the same change. Whether such rows exist is NOT MEASURED.
  • Backend readers (@objectstack/lint field references and semantic roles, metadata-core's field-security fold, metadata-protocol's build probes): no behaviour change for a list with one existing field fewer.

objectui's served-page pin copies (apps/console/src/services/approvalRequestsDataSource.servedPage*.test.tsx and record-details.highlightFieldsFallback-12433.test.tsx) copy the strip by value, and still copy it as it stood before #24264 (process_name … current_step); objectstack-ai/objectui#12484 rewrites them. Nothing in this repository reads them, and none of them imports this package.

Tests

At 5f21f062f (the final head), under scripts/pm/os-verify-lock.sh (VERDICT command-exit 0):

  • pnpm turbo run build --filter='!@objectstack/docs' --concurrency=2: exit 0, 76 successful, 76 total.
  • pnpm --filter @objectstack/plugin-approvals exec vitest run --maxWorkers=2 src/sys-approval-request-page.test.ts: Tests 19 passed (19) (18 before, plus the new case).
  • pnpm --filter @objectstack/plugin-approvals test: Test Files 81 passed (81), Tests 1112 passed (1112).
  • pnpm --filter @objectstack/plugin-approvals typecheck: exit 0, check:test-typecheck: OK.

The same four passed at 770c72940, before the merge.

The pin sys-approval-request-page.test.ts gains one case: neither the strip nor the object's highlightFields lists object_name; the Details tab is a bare record:details node and the object's list minus the strip is exactly record_id, whose referenceVia is object_name; and, as a control, object_name stays a declared, required text field. The existing flow and step cases still hold process_name and current_step out of both lists, so the strip and the object's list carry none of the family's three machine-name fields.

Ablation (one run, then restored), at 770c72940. node scripts/ablation-replace.mjs put object_name back into the page's strip, in its old position. On-disk evidence: anchor x1 → x0, replacement x0 → x1, blob a7d3ac80c4dc → 2f28f5526bba. The pin went red, Tests 2 failed | 17 passed (19): the new case failed with expected { strip: true, declared: false } to deeply equal { strip: false, declared: false }, and the existing case failed on the strip no longer equalling the object's list minus record_id. The restore leg: blob after restore == HEAD (a7d3ac80c4dc), git diff HEAD empty, git status clean. The test imports the page from source, so no build sits between the mutation and the run. No file is left behind by the ablation.

Gates

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 5f21f062f derived 67 commands for this diff (the same 67 as at 770c72940). All 67 ran locally at 5f21f062f, after the full build above, each exit code captured before any pipe: 67 exited 0. Among them, as the claim named them:

  • pnpm check:page-declaration-shape: OK — 34 page entries across 3540 sources … all reach the kernel through a discoverable declaration.
  • pnpm check:i18n: check-i18n-bundles: OK (10 package(s) — all bundles in sync, no undeclared authoring keys). pnpm check:i18n-stale-fill: OK (11 bundle set(s) — no new stale fills, 0 baselined).
  • pnpm check:nul-bytes: OK (scanned 12325 text file(s) … no raw ASCII control bytes).
  • pnpm check:test-source-alias: OK — 76 packages with tests scanned.
  • node scripts/check-platform-object-tenancy-census.mjs (and its --self-test): ✓ platform-object tenancy census matches the tree: 83 platform-namespace objects.
  • pnpm check:published-files: ✓ … 73 publishable package(s). pnpm check:dual-build-cjs-loads: ✓ … 111 published require entry point(s) across 69 package(s) load.

node scripts/pm/dispatch-gates.mjs --ran reconciliation: 67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN.

On the first pass (at 770c72940) three refused with exit 3, PREREQUISITE NOT MET, and passed after their prerequisite: pnpm check:i18n and pnpm check:dual-build-cjs-loads (no full build yet), and node scripts/check-plugin-teardown-shape.mjs --self-test (its positive control is pinned to commit 621a4876, outside this shallow checkout; one git fetch --depth=1 of that commit).

The repo-wide scans (pnpm lint and the rest of the CI battery) and the vitest repo project are CI's; their PR check-runs are the reading. check:dev-prereqs and check:skill-frame-freshness were not run: the diff touches neither packages/spec nor the four-axis frame files.

Acceptance notes

  • The four request list views (all_requests, my_pending, submitted_by_me, completed) still list object_name, so their Object column shows the API name. They are outside the page rule above and outside this card; reported to the seat.
  • The served page's H1 is unchanged by this PR.

Generated by Claude Code

…raws no Object chip

The strip listed `object_name`, the target object's API name. The page
names the target object by its label on the Details tab's pointer-pair
card instead; `object_name` leaves the object's `highlightFields` with
the strip so the Details tab does not draw it as a row.

Claude-Session: https://claude.ai/code/session_01LR1cD9JqgkeqY7k9rrZH8s
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tests tooling labels Oct 19, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/plugin-approvals, touching 8 documentable anchor(s).

21 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 959ee317b13d98c97eba5ef2ac0bb270d8746d93.

⛔ 8 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 6 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 959ee317b13d98c97eba5ef2ac0bb270d8746d93 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 79ee1bc5cd0fbccfb259d6cea594a70c97d2f065 — the merge of head 5f21f062ffdb9c69ea1250078ce1e1cba9419793 into base 959ee317b13d98c97eba5ef2ac0bb270d8746d93, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 79ee1bc5cd0fbccfb259d6cea594a70c97d2f065 && git checkout 79ee1bc5cd0fbccfb259d6cea594a70c97d2f065
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 959ee317b13d98c97eba5ef2ac0bb270d8746d93 5f21f062ffdb9c69ea1250078ce1e1cba9419793 && git checkout -B drift-repro 959ee317b13d98c97eba5ef2ac0bb270d8746d93 && git merge --no-ff 5f21f062ffdb9c69ea1250078ce1e1cba9419793

node scripts/docs-audit/affected-docs.mjs --json 959ee317b13d98c97eba5ef2ac0bb270d8746d93

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 959ee317b13d98c97eba5ef2ac0bb270d8746d93 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · #24375 · domain:services seat 2 · os-warren · session_01LR1cD9JqgkeqY7k9rrZH8s · 2026-10-19T08:36Z · head 5f21f062f, the review of record for claim 6261460653.

  1. Form: draft, base main. The first line is Fixes #24375, and no other closing keyword sits beside any issue number. Clause-②: no stands: the diff removes one entry from the page strip's fields and one from the object's highlightFields. No export, type, accepted value, served key or stored column moves. Not governed, 67 changed lines (check-governed-merges.mjs --pr 24500). The changeset is patch, and each sentence was checked against the diff.
  2. Premise, measured:
    • The route is "no Object chip". It rests on A2: the Details tab's pointer card names the target object by its label.
    • The seat re-read it at objectstack-ai/objectui pin 4016370af3c9: RecordPreviewCard.tsx:251 objectLabel({ name: targetObject, label: def.label || targetObject }), and the pin test "heads it with the object label" (RecordPreviewCard.test.tsx:130). The label is read live from the object's definition, so a relabelled object reads its new label.
    • The four-axis reasons and the label route's measured cost are in the PR body.
  3. Gate check-runs on 5f21f062f at this read: 23 success, 6 skipped, 9 in progress (Lint & Repo Gates, Type Check · workspace, Type Check · consumer gates, Test Core 1/6–6/6). Landing waits for every check to be green or an expected skip on the current head.
    • Local, from the report: page pin 19/19; package 1112/1112; typecheck exit 0.
    • The 67 derived families exited 0 (--ran: 67 run, 0 NOT-MEASURED).
    • Ablation: object_name put back on the strip turns 2 tests red, and the restore was verified by blob hash. The lane's negative-pin commitment is met.
  4. Accepted deviations:
    • sys-approval-request.object.ts is edited on the no-chip route; the claim named it for the label route only. The page's own rule, "the strip and the object's list change together", requires it: without it object_name reappears in the Details tab as a row. The file is in the same package, and no other claim holds it. This record amends the claim's file surface to include its highlightFields.
    • A2 is a static reading, not a browser one. The pin test above is the console's own assertion of the same behaviour.
  5. Out of scope: the list views' Object column (object_name in four sys_approval_request views and two sys_approval_action views, drawn as plain text by ObjectGrid) is filed as plugin-approvals: the request and action list views show an Object column with the target object's API name (object_name, such as showcase_announcement), the type half of the record pointer pair, drawn as plain text #24501 for triage.
  6. Landing: ordinary queue landing. When every check on the current head is green, the seat sends pr_ready + automerge_enable through the relay, confirms MERGED on origin/main, and clears pm:dispatched and the assignee on plugin-approvals: the approval request page's Object chip shows the target object's API name (object_name, such as showcase_announcement), while the get serves object_label, which sys_approval_request does not declare #24375 with a landing note.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 19, 2026 08:55
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 19, 2026 08:55
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 19, 2026
Merged via the queue into main with commit 666283a Oct 19, 2026
41 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-24375-approval-object-chip branch October 19, 2026 09:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s tests tooling

Projects

None yet

2 participants