Skip to content

fix(plugin-approvals): the approval request page's highlights strip names the flow by its label - #24279

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-24264-approval-page-flow-label
Oct 18, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-24264-approval-page-flow-label

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #24264

Clause-②: no

The served sys_approval_request_detail record page's highlights strip now names the flow by its label, process_label (the "Flow" field). Before, it listed the machine source process_name, so its first chip read like flow:deal_desk. The object's highlightFields makes the same swap: process_label takes the place of process_name, and record_id stays on the object's list and off the strip.

Responsibility: plugin-approvals' own declarations (the page's highlights fields and the object's highlightFields) | the platform path already serves the flow label: process_label is declared on the object, served by the approvals get, and read by the record title's formula and every request list | reached by every user who opens a served approval request record page.

The choice: object-level, not page-only

Readings are against objectstack 53ef75243 and objectui at the console pin .objectui-sha = 5e15928b4, by git show and git grep. The page was not driven in a browser.

The page's own Details tab reads the object's list, so a page-only edit is not page-only. The Details tab is a record:details node with neither sections nor fields. The spec declares what such a node draws (RecordDetailsProps, packages/spec/src/ui/component.zod.ts): the object's highlightFields. At the pin, objectui's record-details.tsx (objectui#12433) draws that list minus every field the mounted strip registers and minus the H1 field. Its pin record-details.highlightFieldsFallback-12433.test.tsx names this page as the case it was built for. So the Details tab is the object's list minus the strip:

strip Details tab
main today process_name, object_name, status, current_step, submitter_id, updated_at record_id (the target record card)
page-only process_label, … process_name, record_id: a "Source: flow:NAME" row appears in the tab
object-level (this PR) process_label, … record_id, unchanged

The page's header slot falls through to the synthesizer. Since the page authors highlights, the synthesizer's derived strip and record:path are not emitted (buildDefaultPageSchema's highlights branch), so the header reads nothing from this list.

Other readers of the object's highlightFields, and whether a request surface reaches them today:

  • ObjectView / ObjectGrid default columns: used only by a list view that declares no columns. All four sys_approval_request list views declare columns, and every one already lists process_label. Not reached.
  • InterfaceListPage default columns, ObjectKanban default card fields, and RelatedList default columns and picker: no interface page, kanban view or related list of requests is declared in this repository. Not reached.
  • LookupField dropdown preview columns (deriveLookupColumns): the two lookups to a request, sys_approval_action.request_id and sys_approval_approver.request_id, sit on objects whose enable.apiMethods is ['get', 'list'], so no create or edit form opens that picker. Not reached. The Action History list's request cell draws the request's title, not this list.
  • RecordPreviewCard, the pointer card a referenceVia pair draws: reached only where a pointer pair names a request row, for example an audit or activity row about a request. Whether such rows exist is NOT MEASURED. Where they do, the card shows the Flow label where it showed the Source.
  • Studio's object settings panel shows the list as authored, now with process_label.
  • Backend readers: @objectstack/lint (field references and semantic roles), metadata-core's field-security fold of the served schema, and metadata-protocol's build probes. None of these changes behaviour for a list of existing fields. The page test's build-rule leg stays green.

objectui's console served-page pin copies (apps/console/src/services/approvalRequestsDataSource.servedPage*.test.tsx at the pin) copy both lists with process_name. objectstack-ai/objectui#12484 rewrites them, and they are not touched here.

The null case, within declared fields

The console reads a request through the approvals get (routeApprovalRecordReads, apps/console/src/AppContent.tsx at the pin). There, rowFromRequest serves process_label by the one chain in display-labels.ts (requestDisplayLabels): the stored label, else the node-config snapshot's __flowLabel, else process_name humanized (flow:deal_desk becomes Deal Desk). approval-service.test.ts pins that fallback ("rows fall back to prettified machine names when labels are absent"). What a user sees:

  • A label is stored, or the snapshot carries one: the Flow chip shows the flow's authored label.
  • No label stored (a row written before the column, before or after a failed boot fill): the Flow chip shows the humanized machine name, never flow:NAME.
  • The chain yields nothing: this needs a process_name that humanizes to nothing (flow: followed by nothing), and openNodeRequest never writes one (it writes flow: plus the flow name or the node id). The field then has no value, and the console's strip draws no chip for a field with no value in read mode, so there is no Flow chip. The record title still names process_name: on the approvals get through titleFormat, since that get serves no display_title, and on the data API through display_title's fallback.
  • The same record read through the generic data API (a host without the console's routing): process_label is the stored column, null on a row the boot fill has not labelled. The chip is then absent, and display_title reads process_name · record_id.

No other field fills the chip. No field, formula or column is added, and there is no spec row: record:highlights fields already admits the field.

Tests (at 046ca5b66)

  • pnpm --filter '@objectstack/plugin-approvals^...' build: VERDICT command-exit 0. pnpm --filter @objectstack/plugin-approvals build: exit 0.
  • pnpm --filter @objectstack/plugin-approvals exec vitest run --maxWorkers=2 src/sys-approval-request-page.test.ts src/sys-approval-request.object.test.ts src/sys-approval-display-title.test.ts: 3 files, 36 tests passed.
  • pnpm --filter @objectstack/plugin-approvals test: Test Files 81 passed (81), Tests 1108 passed (1108). The package has 81 test files.
  • pnpm --filter @objectstack/plugin-approvals typecheck: exit 0, with check:test-typecheck: OK.
  • The pin sys-approval-request-page.test.ts moves. Its existing case still holds the strip to the object's list minus record_id. A new case asserts three things: both lists lead with process_label, neither lists process_name, and the object's list minus the strip is exactly record_id (the Details tab).

Ablation (one run, then restored). node scripts/ablation-replace.mjs put process_name back at the head of the page's strip. Its on-disk evidence: anchor x1 → x0, replacement x0 → x1, blob aea7e3212b97 → 822555700a37. The pin went red: Tests 2 failed | 15 passed (17). The new case failed with expected 'process_name' to be 'process_label', 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 (aea7e3212b97), git diff HEAD empty, git status clean. No test file is left behind by the ablation.

Gates

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 046ca5b66 derived 67 commands for this diff. All 67 ran locally at 046ca5b66, each exit code captured before any pipe:

  • 65 exited 0 on the first pass.
  • Two first refused with exit 3, PREREQUISITE NOT MET: pnpm check:i18n (the CLI was not built) and pnpm check:dual-build-cjs-loads (packages without dist/). After the prerequisite builds (the i18n gate's own one-command closure, then turbo run build --filter=!@objectstack/docs), both exited 0. check-i18n-bundles: OK (10 package(s) — all bundles in sync, no undeclared authoring keys); ✓ 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.

The repo-wide scans (pnpm lint and the rest of the CI battery) are CI's. Their PR check-runs are the reading.

Acceptance notes


Generated by Claude Code

…ames the flow by its label

The served sys_approval_request_detail page's highlights strip listed
process_name (the machine source, flow:NAME) where the flow's label,
process_label, is declared and served. The strip and the object's
highlightFields now both lead with process_label; record_id stays on the
object's list and off the strip.

The object's list moves with the strip because the page's Details tab is
a bare record:details node, which draws the object's highlightFields
minus the strip: a strip-only change would have moved process_name into
that tab.

Claude-Session: https://claude.ai/code/session_01Nk17Z26X5DB59iQztDBADH
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 18, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/plugin-approvals, touching 9 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 93b4acb57ec23c4e94a7047133ce612f89a13838.

⛔ 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 93b4acb57ec23c4e94a7047133ce612f89a13838 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from be111dc14f0b40234eb8a685df7c7d70c23e6513 — the merge of head 046ca5b6672da75d3562ba9b1741370da96ce39a into base 93b4acb57ec23c4e94a7047133ce612f89a13838, 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 be111dc14f0b40234eb8a685df7c7d70c23e6513 && git checkout be111dc14f0b40234eb8a685df7c7d70c23e6513
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 93b4acb57ec23c4e94a7047133ce612f89a13838 046ca5b6672da75d3562ba9b1741370da96ce39a && git checkout -B drift-repro 93b4acb57ec23c4e94a7047133ce612f89a13838 && git merge --no-ff 046ca5b6672da75d3562ba9b1741370da96ce39a

node scripts/docs-audit/affected-docs.mjs --json 93b4acb57ec23c4e94a7047133ce612f89a13838

⚠️ 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 93b4acb57ec23c4e94a7047133ce612f89a13838 → 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 · domain:services seat 1 (#6021) · marchtian · session_01Nk17Z26X5DB59iQztDBADH · 2026-10-18T08:10Z · head 046ca5b667. The seat's own review at TIER_DEFAULT: --tier read no path-derived mandate, and the diff touches no published schema or governed text. It lands through the merge queue once every check on this head is green.

  • Shape: a draft PR to main. Its first line is Fixes #24264, and that is the only closing keyword in the body. Clause-②: no is at a line start in the body and in the changeset (@objectstack/plugin-approvals patch). Card and PR assignee: marchtian. Not governed: check-governed-merges reads none of the 4 paths, and 57 changed lines ≤ 3000.
  • The card's Done when, each met:
    1. The strip names the flow by its label. sys-approval-request.page.ts lists process_label where it listed process_name.
    2. The null case, within declared fields. The seat read the chain on main. The approvals get serves process_label by requestDisplayLabels (display-labels.ts:70): the stored label, else the snapshot's __flowLabel, else the humanized machine name. So a row the boot fill never labelled still shows a Flow chip. Only a value that humanizes to nothing leaves no chip, and the record title still names process_name then. No field, formula, column or spec row was added.
    3. The page pin moves with it. A new case holds the strip and the object's list to process_label, and the object's list minus the strip to record_id alone. The dev's ablation (putting process_name back) turned 2 cases red, and the restore is blob-equal to HEAD.
    4. Object-level, with the surfaces measured. The seat confirmed the deciding fact: the Details tab is a bare record:details node, and the spec declares that such a node falls back to the object's highlightFields (component.zod.ts:1290). A strip-only edit would have moved process_name into that tab. The other readers of the object's list are named in the PR body. The ones it reaches (a request's preview card, Studio's object settings) now show the label. Each first-party request list declares its own columns.
  • Gates on this head: 34 checks succeeded and 6 were skipped by design (Build Docs, Console Pin Gate, Dogfood Regression Gate, Dogfood Verify CLI, Packed-tarball smoke, Temporal Conformance: the roster set). There were no failures, and the required contexts are green. Locally the dev derived 67 families on this head and ran all 67. Two first exited 3 on an unbuilt prerequisite (check:i18n, check:dual-build-cjs-loads) and passed after their own prerequisite builds (--ran: 0 NOT-MEASURED, 0 UNRUN).
  • Accepted deviations:
    • The route is object-level, not the seat's suggested page-only one. The Details-tab coupling above is the measured reason, and the claim's file surface allowed it.
    • The page header's tabs bullet was corrected in the same comment block. It said the Details tab draws "the request's own fields by the object's field groups", which the measurement contradicts.
  • Out-of-scope findings, with their carriers:

Generated by Claude Code

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