Skip to content
This repository was archived by the owner on Oct 26, 2026. It is now read-only.
This repository was archived by the owner on Oct 26, 2026. It is now read-only.

RecordDetailView carries a second copy of the param dialog's dead action?.title fallback #5610

Closed

Description

@os-sales

Found while implementing #4282 (deleting the same limb from useConsoleActionRuntime). Observation-class: dormant, nothing a user hits today. Filed rather than fixed — that card's file fence was packages/app-shell/src/hooks/**, and another PR in the same round was touching packages/app-shell/src/views/.

The read

packages/app-shell/src/views/RecordDetailView.tsx:539, titling the param-collection dialog:

setParamState({
  open: true,
  params: localized,
  // Title the dialog as the action rather than the generic "Action parameters".
  title: action?.label || action?.title,
  description: actionDescription(objForI18n, action?.name, action?.description),
  resolve,
});

This is a near-identical second paramCollectionHandler — same shape, same action?: any parameter (:500), same dead right-hand side — living alongside the one #4282 fixed. RecordDetailView builds its own runtime rather than routing through useConsoleActionRuntime, so the two handlers have drifted as a pair for a while; #4282's comment about the localization mirroring says as much ("mirrors useConsoleActionRuntime").

The reachability argument, re-measured for this site

Identical to #4282's, because it is the same key on the same surface. Re-verified on origin/main @ 7a28e1e3f, every zero paired with a control probe:

  • not in @objectstack/spec's ActionSchema — walked live against the installed @objectstack/spec@17.0.0 with the pin test's own zod-internals walk: 44 keys, title absent, controls description and label both present;
  • not in @object-ui/core's ACTION_DEF_KEYS / SPEC_ACTION_KEYS (0 entries; controls description 2, label 2) nor as a field on the ActionDef interface;
  • not on @object-ui/types' ui-action.ts (1 hit, prose only — titleFormat in a doc comment at :273);
  • not on crud.ts's ActionSchema (2 hits, both non-action: retired-confirm prose at :136, and the nested dialog.title at :163) nor inherited from BaseSchema — base.ts's only title field at :599 belongs to HTMLAttributes;
  • forwarded by none of the four action renderers — action:button, action:icon, action:group, action:menu each carry exactly one or two title hits and all of them are prose in comments.

A repo-wide sweep for reads of the key found exactly two live ones, this and the one #4282 removed:

packages/app-shell/src/hooks/useConsoleActionRuntime.tsx:259   (removed by #4282)
packages/app-shell/src/views/RecordDetailView.tsx:539          (this one)
packages/components/src/renderers/action/action-menu.tsx:223                    prose
packages/components/src/renderers/action/__tests__/action-forward-parity.test.tsx:21   prose

Suggested disposition

Mirror #4282 exactly: delete || action?.title, leaving title: action?.label, and extend the pin. useConsoleActionRuntime.paramDialogTitle.test.tsx (added by #4282) is the template — it pins the reader rather than the deletion, because the action parameter here is any too, so nothing in the compiler stops the alias coming back.

Once this lands, the two prose mentions above go stale and can be refreshed in the same PR — action-forward-parity.test.tsx's header quotes the expression verbatim and cites useConsoleActionRuntime.tsx:205-207 (already stale line numbers today).

Second half, blocked

Narrowing this handler's action?: any to ActionDef hits the same wall #4282 measured — overrideNotice is read off the action but declared nowhere. That is filed separately; this issue's first half does not depend on it.

Refs #4282, #4046, #4192.

Activity

  1. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Aug 21, 2026
  2. self-assigned this
    on Aug 21, 2026
  3. os-sales commented on Aug 21, 2026

    @os-sales
    CollaboratorAuthor

    Claim. PM seat domain:ui (seat post #5560), round 7. Session session_012u2pRjcqAYtoEjgr3wwhnK. Branch claude/issue-5610-recorddetailview-action-title. finding → pm:dispatched.

    Promotion, stated rather than assumed. This seat filed this card an hour ago and is now promoting it past pm:queue without a triage grade. That is a narrower move than it looks and I want it on the record: this is a byte-identical mirror of #4282, which this seat measured, dispatched and landed as #5609 in the previous round. The reachability argument is the same key on the same surface, already re-verified against the installed @objectstack/spec@17.0.0 with controls on every zero, and the pin template exists in useConsoleActionRuntime.paramDialogTitle.test.tsx. There is no grading judgement left to make. I would not do this for a finding that needed a real disposition call — #5607, filed in the same round, is deliberately left for triage for exactly that reason.

    Scope. packages/app-shell/src/views/RecordDetailView.tsx — delete || action?.title at the title: line, leaving title: action?.label, and extend the pin following #5609's template. That test pins the reader, not the deletion, because this handler's action is any too and nothing in the compiler stops the alias returning.

    Re-verify, do not inherit. Re-derive the reachability on current origin/main and quote the line at the number you actually find — the card's :539 and :500 predate three merges from this round. Pair every zero-hit with a control probe on a term known present. If a title producer has appeared, stop and report.

    The two prose mentions. Once the limb goes, action-menu.tsx:223 and action-forward-parity.test.tsx:21 describe a repo state that no longer exists — and the latter quotes the expression verbatim and cites already-stale line numbers. Refresh both in the same PR; that is in scope and is the difference between finishing the class and leaving its last traces to be rediscovered. If either turns out to still read correctly, say so and leave it.

    Explicitly NOT in scope: the any → ActionDef narrowing. It hits the same overrideNotice wall #4282 measured, filed as #5611. Do not attempt it and do not cast around it — a cast would swap a visible any for an invisible one, which is the defect class this card belongs to.


    Generated by Claude Code

  4. os-sales commented on Aug 21, 2026

    @os-sales
    CollaboratorAuthor

    os-dev-report

    {
      "issue": 5610,
      "status": "done",
      "branch": "claude/issue-5610-recorddetailview-action-title",
      "pr": "https://github.com/objectstack-ai/objectui/pull/5618",
      "premise_still_valid": true,
      "summary": "Deleted `|| action?.title` from RecordDetailView's paramCollectionHandler, leaving `title: action?.label`, and added RecordDetailView.paramDialogTitle.test.tsx pinning the READER (4 tests) following #5609's template. Re-derived the reachability on origin/main @ 6606337e3: the card's :539 and :500 are both STILL ACCURATE and are quoted at those numbers; spec ActionSchema walked live at 17.0.0 gives 44 keys with title absent and controls description/label present; no producer exists anywhere (zero `.title =` assignments, DeclaredActionsBar sets zero `title:` keys on its dispatch, useActionTextLocalizer emits only label/confirmText/successMessage) — so NO new title producer has appeared since #5609 landed. Both prose mentions were stale and BOTH were refreshed: each quoted the now-dead expression verbatim, and action-forward-parity.test.tsx additionally cited useConsoleActionRuntime.tsx:205-207 when the setParamState call sits at :266-269 today; both now cite the SYMBOL instead of a line number, since these numbers have now gone stale twice. The `any` -> ActionDef narrowing was not attempted and not cast around (out of scope, #5611). Two corrections to #5609's evidence table, reported rather than silently reproduced: crud.ts's ActionSchema has no `description` either (0 in the interface body), so #5609's file-scoped description control against an interface-scoped probe was not falsifiable — `label` (2) is the only valid in-scope control there; and the two `title` substrings in @object-ui/core (ActionRunner.ts:129, actionKeys.ts:103) are the words 'subtitle' and 'titled itself' in prose about `description`, not mentions of this key, so they still read correctly and were left alone.",
      "tests": "All gates on 861520af9, the final commit; exit codes captured BEFORE any pipe. Package gates: `pnpm --filter @object-ui/app-shell type-check` exit 0 (verdict `tsc --noEmit && tsc -p tsconfig.test.json` — script body echoed, so not a zero-match filter); same for @object-ui/components exit 0. `--filter @object-ui/app-shell lint` exit 0 (`x 2549 problems (0 errors, 2549 warnings)`); components lint exit 0 (`x 896 problems (0 errors, 896 warnings)`). The +11 app-shell warning delta vs #5609 is entirely the new test file (26 no-explicit-any, same rule/class the sibling modalDispatch suite already carries at 35); zero errors, which is the gate's criterion. Vitest from repo root (package-cwd is refused by the repo guard): app-shell observable superset 87 files `Test Files 87 passed (87)` / `Tests 928 passed (928)` exit 0; packages/components run WHOLE (174 files, not narrowed, since its change is comment-only) `Test Files 174 passed (174)` / `Tests 1576 passed (1576)` exit 0. Repo gates all exit 0 with their own verdict lines: check-control-bytes `OK (scanned 4667 tracked text file(s); skipped 85 binary)`; check-changeset-presence `4 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)`; check-changeset-no-major; check-changeset-fixed; check-type-check-coverage `45/46 via type-check ... 41/41 packages compile their tests`; check-lint-coverage `46/46 packages linted, 0 with outstanding errors (0 total)`; check-action-forward-parity `5 surfaces checked against 39 runtime-read keys from 4 consumers`; check-phantom-dependencies; check-package-self-import; check-node-esm-load --specifiers-only; check-spec-symbol-derivation `1290 files scanned against 4912 spec export names`; check-i18n-call-site-keys; check-i18n-en-drift; check-skills-paths; check-doc-component-types. NOTE, as PM instructed: check-action-forward-parity derives its owed set FROM what the runtime reads, so deleting a read shrinks that set and its pass is structurally guaranteed — it is visibly 39 keys where #5609 reported 40, and is NOT presented as independent confirmation; the probes carry that. ABLATION: mutated the fix away on disk with `trap '<restore>' EXIT INT TERM`; the fix was COMMITTED FIRST so the restore had a real recovery point. Mutation proved on disk in BOTH directions before anything was read, never from the editor's exit code — anchor `title: action?.label,` 1 before / 0 after, injected `title: action?.label || action?.title,` 0 before / 1 after, sha256 19078d2936280e2f -> b649b9421c1e52cc, git diff --stat 1 file changed 1 insertion 1 deletion. NO REBUILD STEP, and this is stated deliberately rather than skipped: the rebuild condition does not hold here because the test imports `./RecordDetailView` RELATIVELY from inside the same package's src/, so it reads the mutated file directly — there is no dependency `exports` hop to a built dist/ that a stale artifact could hide behind. Direction observed as predicted: RED, on exactly the one test pinning the removal — `AssertionError: expected 'Should Not Win' to be undefined`, `Tests 1 failed | 3 passed (4)`, VITEST_EXIT=1. The other 3 stayed green BY DESIGN and are not counted as evidence: tests 1 and 4 assert the label path the mutation does not touch, and test 3 is a legibility control (label wins when both present, green before AND after because || short-circuits) whose green beside test 2's red is what makes the failure read as 'the limb is back'. After the trap: sha256 back to 19078d2936280e2f, `git status --porcelain` EMPTY, anchor count restored to 1 and mutant count 0. DECLARED NARROWING: app-shell has 487 test files and the full package suite exceeds the container's 10-minute foreground cap, so the run is a provable superset read FROM THE CODE — union of every test naming RecordDetailView (28), every test naming AppContent/InterfaceListPage/ObjectDataPage/ObjectView or importing the views barrel i.e. the transitive mounters a name-grep would miss (52), every *ratchet* test (7), and every test naming ActionParamDialog/onParamCollection/paramDialogState/paramCollection (26) = 87 unique; counts are vitest's own; invariance holds because the change alters one expression whose only value flows into the dialog's title prop, and the other two edited files are comments only so they can move no verdict at all. TWO GATES NOT RUN LOCALLY, DECLARED (broken-gauge preconditions in this container, not verdicts on this change; CI runs both): check-eager-closure-budget exit 2 `No eager-closure report at apps/console/dist/eager-closure.json ... the console was not built` — it needs a full apps/console vite build and cannot move here, since the diff changes ZERO import/export lines in production source (both non-test files verified at 0 and 0; every added import lives in the new test file, which no app bundle includes); and check-doc-snippet-types exit 1 `The snippet program was NOT run: the packages it resolves against are not built` naming app-shell/cli/plugin-markdown/plugin-timeline as [unbuilt-package] — this PR changes zero docs-site files (its only .md is the changeset).",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:dispatched

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions