Skip to content

console: a type: "api" action with opensInNewTab + newTabUrl opens no tab and POSTs to its target instead #26747

Closed

Description

@objectstack-fleet

Filing gate: ① product defect, reproduced, landing site named.
Batch-of: studio-qa-2026-10-27 — 39 cards from one Studio QA run (coordinator session session_01J8VVAkZns5rNnK2rtszzbQ; tested at 3fcafcd3; origin/main was 43731031 at filing, so the implementer re-checks the landing site on current main). The run record is the qa-run issue filed with this batch.

Symptom

The declared new-tab behaviour is ignored from both the record header and the more-menu.

Reproduction

  1. Action export_tab on gapdata_item: type: "api", opensInNewTab: true, newTabUrl: "https://example.com/export/{recordId}" → click from the header and from the more-menu → no tab opens; network shows a POST to the target.

Expected

The runner opens newTabUrl (interpolated) in a new tab as the spec documents, or the spec refuses the pair on api actions.

Suspected landing site (the implementer verifies)

Console action runner for api actions under ui/.

Filing facts

Activity

  1. added
    bugSomething isn't working
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Oct 27, 2026
  2. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade — priority:p2 · domain:ui · area:records · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-27T17:04Z

    ⛔ Not a claim, ⛔ not a dispatch.

    • p2: a type: "api" action with opensInNewTab + newTabUrl opens no tab and POSTs instead. Honour the declared new-tab behaviour on every surface that runs actions. From the studio-qa-2026-10-27 batch. Re-check the landing site on current main.

    Generated by Claude Code

  3. added
    area:recordsBusiness objects, records, the views that show data, usable forms, search
    domain:uiDomain lane — seat card indexed by label:pm:seat
    pm:queueReady for the PM dispatch loop
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    pm:queueReady for the PM dispatch loop
    on Oct 27, 2026
  4. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01QsR2jTfvjNTaR29ehLk26T
    Account: zhuangjianguo
    Branch: claude/project-thread-bol9ec (platform-assigned card-thread branch; a card thread can push only its assigned branch, and the name carries no issue number, so this claim is the identity record)
    Worktree: objectstack-issue-26747
    Domain: domain:ui
    Seat: domain:ui#4
    File surface: whichever of ui/packages/app-shell/src/utils/consoleServerAction.ts, ui/packages/app-shell/src/hooks/useConsoleActionRuntime.tsx or ui/packages/core/src/actions/ActionRunner.ts holds the break (named in the report), tests beside, one root changeset; ⛔ a second interpolator; ⛔ packages/spec/**, server packages, any new key (stop on breach; explain in the report)
    Container & model: S, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate; reason: the maintainer's standing rule 「只认领不需要 fable 的卡」, 2026-10-27T10:39Z), effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: the console action dispatch (ui, this lane) POSTs an api action that declares opensInNewTab + newTabUrl instead of opening the tab, which breaks the spec's documented zero-roundtrip path · the platform path that may cover it: the newTabUrl fast path in consoleServerAction.ts (~181-210), which this dispatch does not reach · reached by every declared new-tab api action on the record header and more-menu
    Thread-read: 6448334618
    Premise: holds (consoleServerAction.ts holds the fast path at ~188-208, yet QA G8 measured a POST from the header and more-menu on origin/main 6090bb73 at 2026-10-27T20:24Z)
    Serial constraints cleared: no open PR touches the surface (the thread re-checks at start)

    Dispatched at 2026-10-27T20:29Z to the card thread on that branch; the order lives in the thread's opening message.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim amendment · domain:ui#4 · 2026-10-27T20:31Z

    Thread-read: 6452823300

    The card thread (claude/project-thread-bol9ec) measured the break. The console registers two api handlers, and neither reads opensInNewTab / newTabUrl; both always fetch the target. The list pages run the one in useConsoleActionRuntime.tsx, already in the surface. The record header and the ⋯ more-menu run RecordDetailView.tsx's own apiHandler, passed in its ActionProvider handlers map, which was outside the surface. The fix exports the fast path's own condition from consoleServerAction.ts. Both handlers then route an api action that meets it to the existing console server-action handler, which already resolves the record id, pre-opens the tab and substitutes {recordId}. No second interpolator.

    File surface: ui/packages/app-shell/src/utils/consoleServerAction.ts (the exported predicate), ui/packages/app-shell/src/hooks/useConsoleActionRuntime.tsx, ui/packages/app-shell/src/views/RecordDetailView.tsx (its apiHandler only), tests beside (including RecordDetailView.apiNewTabUrl-26747.test.tsx), one root changeset; ⛔ ui/packages/core/**, packages/spec/**, server packages, any new key (stop on breach)
    Clause-②: no
    Metadata-protocol: none

    An absolute newTabUrl (QA's value) is a separate open question. The spec calls the key a path template, and the fast path prefixes baseUrl. That behaviour is not changed here.

  6. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 26747,
      "status": "done",
      "branch": "claude/project-thread-bol9ec",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/26922",
      "session": "session_01QsR2jSH8FbKjEztmGT1sbB",
      "premise_still_valid": true,
      "summary": "The failing link is the console's registered type:'api' handlers, not the runner or the surfaces: ActionRunner.dispatchCollected hands an api action to the registered handler, and both api handlers (useConsoleActionRuntime.apiHandler for list surfaces and ConsoleShell's root provider, RecordDetailView.apiHandler for the record header and its more-menu) always fetched target and never read opensInNewTab/newTabUrl; the fast path existed only in the script-registered handler built by createConsoleServerActionHandler. New utils/newTabUrlFastPath.ts holds the rule: takesNewTabUrlFastPath (now also the fast path's own condition in consoleServerAction.ts) and withNewTabUrlFastPath(apiHandler, consoleHandler), which routes such an action to the surface's console server-action handler (its record-id resolution, pre-opened tab and {recordId} substitution; no second interpolator) and every other api action to apiHandler unchanged, forwarding the fast path's row-count refusal only for those actions. Both hosts register the composed handler; the hook also returns it as apiHandler.",
      "tests": "head 1bcc587b. Build closure turbo build app-shell^... + formula/client/sdui-parser/lint: 63 tasks, VERDICT command-exit 0. pnpm --filter @object-ui/app-shell run type-check (covers tests): exit 0 at 1bcc587b. New files RecordDetailView.apiNewTabUrl-26747 (real header click, real more-menu click, control) + useConsoleActionRuntime.apiNewTabUrl-26747 (real ActionRunner list toolbar, returned apiHandler, two-row refusal, control): Test Files 2 passed (2), Tests 7 passed (7). Ablation via ablation-replace.mjs on newTabUrlFastPath.ts ('? consoleHandler(action, context)' -> '? apiHandler(action, context)', anchor 1->0, blob 3ef0dcca->087c133d): Tests 4 failed | 3 passed (7), exactly the four fast-path cases; restored blob == HEAD, git diff HEAD empty (app-shell tests import src, no dist leg). Narrowed app-shell run, 100 files (test files named for useConsoleActionRuntime / consoleServerAction / RecordDetailView / ConsoleShell + every app-shell test naming apiHandler or newTabUrl): Test Files 100 passed (100), Tests 736 passed (736), VERDICT command-exit 0; first run at 0c1ffeb2 was 19 failed files (stubs of utils/consoleServerAction lacked the new export), fixed by moving the rule to its own module. Rest of app-shell left to CI Test UI (declared narrowing). ESLint on the 5 touched ui files: 0 errors, no warning on changed source lines.",
      "gates": "dispatch-gates --commands (7 paths): 34 derived; 33 node/pnpm commands exit 0 (each captured before any pipe) + pnpm --dir ui exec vitest run scripts/ exit 0 (Test Files 178 passed | 2 skipped, Tests 5065 passed | 2 skipped). --ran: 34 derived, 34 run, 0 NOT-MEASURED. check-platform-checklist --changed-from origin/main: 0 anchored files. ui check:new-line-citations: 0 new. PR-body gates before pr_create: check-changeset-no-major, check-adr-0087-registration, check-closing-keyword-parity, check-partof-closing-keyword all exit 0. CI check-runs: in_progress at report time.",
      "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
      "mcp_calls": "5 — read-only: issue_read x4 (get x1, get_comments x3), search_pull_requests x1; no MCP write",
      "api_writes": "2 — POST /repos/objectstack-ai/objectstack/dispatches x2 (fleet-write relay: pr_create with assignees, run 40587029780, read-back identical 8581 bytes, assignee zhuangjianguo; this os-dev-report comment). git push not counted. Reviewer request not made (MCP writes out of bounds).",
      "open_questions": [
        {
          "question": "newTabUrl given as an ABSOLUTE URL (QA's repro: https://example.com/export/{recordId}). The spec describes newTabUrl as 'A path template' and the fast path prefixes the backend origin, so an absolute value now opens a tab at origin + the absolute URL. Refuse it at authoring, or navigate it verbatim?",
          "options": [
            "A: refuse at authoring (spec refinement: newTabUrl must start with /), matching 'A path template' and the auth-enforcing same-backend endpoint the key exists for (cloud /sso-open); loud for AI-written metadata",
            "B: navigate an absolute http(s) value verbatim in the fast path, as the api handlers already treat an absolute target; widens the key's meaning without a spec change",
            "C: leave as is"
          ],
          "recommendation": "A, because the spec text already says path template and the endpoint must enforce auth itself, so an off-origin value is an authoring mistake that should fail loudly rather than be tolerated in the renderer (contract-first); it needs a spec card (Fable tier), not this ui card."
        }
      ],
      "out_of_scope_findings": [
        "class: none filed · opensInNewTab on a type:'api' action WITHOUT newTabUrl (pre-open, then follow a handler-returned redirectUrl) is not wired for api actions either; read-only observation, noted in Acceptance notes, carrier: none",
        "class: none filed · core ActionRunner.executeAPI (non-console hosts with no registered api handler) also ignores the pair; read-only, carrier: none"
      ],
      "deviations": "File surface: the seat's amendment 6452857550 added RecordDetailView.tsx (apiHandler) and its test; the RecordDetailView change composes the handler next to serverActionHandler and swaps the handlers-map entry rather than editing apiHandler's body (apiHandler is declared before serverActionHandler). The rule lives in a NEW file utils/newTabUrlFastPath.ts instead of consoleServerAction.ts, forced by 19 existing tests that factory-stub utils/consoleServerAction (exporting it there made them throw on mount). A second new test file sits beside useConsoleActionRuntime (hooks/__tests__/useConsoleActionRuntime.apiNewTabUrl-26747.test.tsx) for the list-toolbar acceptance. Commit trailers: model-free pair.",
      "files_changed": [
        ".changeset/26747-api-action-new-tab-url.md",
        "ui/packages/app-shell/src/utils/newTabUrlFastPath.ts",
        "ui/packages/app-shell/src/utils/consoleServerAction.ts",
        "ui/packages/app-shell/src/hooks/useConsoleActionRuntime.tsx",
        "ui/packages/app-shell/src/views/RecordDetailView.tsx",
        "ui/packages/app-shell/src/hooks/__tests__/useConsoleActionRuntime.apiNewTabUrl-26747.test.tsx",
        "ui/packages/app-shell/src/views/RecordDetailView.apiNewTabUrl-26747.test.tsx"
      ],
      "line_budget": "552 changed lines (+546 / -6) across 7 files, under the 3000 human-merge threshold"
    }

    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

area:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingdomain:uiDomain lane — seat card indexed by label:pm:seatpm:dispatchedDispatched to a dev agent by /pm-dispatchpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions