Repository navigation
fix(app-shell,console): the tool preview opens the API console inside the app, with its request preset (objectui#10591) - #10632
Conversation
… app, with its preset The tool preview's "Open in API Console" link pointed at a bare /developer/api-console URL, which the console's catch-all redirects home. It now points at the developer:api-console registry key inside the current app, built through componentRefToUrlSegments and rendered through the router so the mount basename applies. ApiConsolePage reads the path and method preset from the query string once on mount and pre-fills the request editor; a path that does not start with /api/ is ignored, and nothing is sent. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BP8CMtACxTdLjqR6rhd33C
…nt app Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BP8CMtACxTdLjqR6rhd33C
…d end to end Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BP8CMtACxTdLjqR6rhd33C
…atch) Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BP8CMtACxTdLjqR6rhd33C
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: Rendered by an isolated review subagent spawned by the ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #10591
Clause-②: no
What changed
The tool preview's "Open in API Console" link (Studio, Tools, a tool, the preview's toolbar) now opens the API console inside the current app, and the API console pre-fills the request the link carries.
@object-ui/app-shell,ToolPreview.tsx. The link was the bare/developer/api-console?path=.... The console's root route table has no/developerroute, so itspath="*"catch-all redirected the author to/. The link now points at thedeveloper:api-consoleregistry key inside the app in the URL:/apps/APP/component/developer/api-console. The path is built fromuseParams().appNameandcomponentRefToUrlSegments('developer:api-console'). The query is built withURLSearchParamsand carriespath=/api/v1/ai/tools/NAME/executeandmethod=POST. The link is rendered through react-router'sLink, so the router basename applies. It still opens in a new tab. With no router above the preview (the designer gallery), or on a route with no:appName, no link is drawn.@object-ui/console,ApiConsolePage.tsx. The page readspathandmethodfromuseSearchParams()once, in a lazyuseStateinitialiser. It pre-fills the URL field and the method selector and sends nothing. The rule for the preset is written in the page header:pathis honoured only when it starts with/api/. An absolutehttp://...URL, a protocol-relative//hostURL, any other path and an empty value are ignored.methodis read only together with an honouredpath, in any letter case. It is used only when it is one of the verbs the selector offers. The selector now renders that same list (REQUEST_METHODS).path, the page opens exactly as before.patchchangeset covers both packages.Nothing else moves. There is no route-table change in either
AppContent.tsxor inApp.tsx. How the page sends requests and keeps its history is unchanged.Measurements behind the design (the dispatch's M1 to M4)
ToolPreviewrenders insideResourceEditPageat/apps/APP/metadata/tool/NAME. Framework's Studio app (studio.app.ts) namesmetadata:resourcewithtype: 'tool', and the nav resolves that to/apps/APP/metadata/tool. Both of app-shell'sAppContentbranches mountmetadata/:type/:nameunder/apps/:appName/*: the with-app branch and the no-active-app branch. So:appNameis always in the URL there. The/studio/:packageId/:tabdesign surface lists no tools. The designer gallery (preview-gallery.tsx) mounts previews with no router. Source chosen:useParams().appName, the URL's own app segment.ResourceListPageand most app-shell views build their/apps/...links from that same segment. For the key-to-URL step I used the existingcomponentRefToUrlSegments. I added no new link helper.component/:ns/:name/*(ComponentNavView) in every app. Thedeveloper/api-consolepath is declared in the console'ssystemRoutes, a detail of one host. The registry key is also what Studio's Developer navigation names, so the link and the sidebar's "API Console" entry reach the same path.ComponentNavViewalso forwards the query as props.ApiConsolePagetakes no props and ignores them. The query read goes through the router instead, so it works on both mounts./developer/...links.IntegrationsPagebuilds${basePath}/developer/api-consolewithbasePathfrom:appName. It is only mounted under/apps/:appName, so the prefix is always there, and it navigates through the router. That is not a defect, and it is outside the claimed files, so I left it alone. The same sweep found an app-less link of the same class in a sibling preview: see Findings.Pins (real components, no module mock of the link builder)
packages/app-shell/src/views/metadata-admin/previews/ToolPreview.test.tsxhas a new describe block. It renders the realToolPreviewin aMemoryRouterunder/apps/:appName/*:/apps/studio/component/developer/api-console, which is the component URL ofdeveloper:api-console;com.acme.crm);path=/api/v1/ai/tools/delete_all_orders/executeandmethod=POST;basename="/_console"the href starts with/_console/apps/...;target=_blankandrel=noreferrer;apps/console/src/pages/developer/ApiConsolePage.requestPreset-10591.test.tsxis new. It uses the real page, the realregisterDeveloperComponents, app-shell's realDefaultAppContentand the console's realsystemRoutes. The shell chrome is stubbed the wayorphanedPageComponentRefs-10520.test.tsxstubs it:/ai/tools/(endpoint discovery still runs);/api/and an empty value;component/developer/api-consoleanddeveloper/api-console;App.tsx's root table is not exported, so a probe stands in for its catch-all.Reverse proofs
Each proof ran through
../objectstack/scripts/ablation-replace.mjsin wrap mode, from committed state. Each anchor hit once (1 to 0). The restore was proven by blob equal to HEAD and an emptygit diff HEAD.ToolPreview(the pre-fix template string). 7 red: six link pins in app-shell and the console end-to-end pin. The no-link, new-tab and harness-control pins stayed green. 7 failed and 21 passed of 28./api/guard dropped. 3 red: absolute, protocol-relative, and outside/api/. The empty-value case stays green because the null check still refuses it. 3 failed and 9 passed of 12.Local verification (all at head
c0052db4f)pnpm exec turbo run build --filter='@object-ui/console^...' --concurrency=2: 34 of 34 tasks successful. That includes app-shell's owntsc && check-dist-completenessbuild.pnpm --filter @object-ui/app-shell type-check: exit 0.pnpm --filter @object-ui/console type-check: exit 0. I counted with--listFiles: app-shell'stsconfig.test.jsonprogram includesToolPreview.test.tsx, and the console's program includes the new test.pnpm --filter @object-ui/console build(tsc && vite build && build:plugin): exit 0. The post-build plugins reported: declared-lazy-views 11 of 12 lazy with 1 eager pinned, types-zod still lazy, eager-closure report emitted.ToolPreview,ApiConsolePage, the preview registry,registerDeveloperComponentsor the preview samples: 33 files and 404 tests passed.@object-ui/app-shellpackage test. It has 799 test files, which does not fit the foreground cap. I declared it to CI's shardedpnpm test.check-changeset-presence,check-changeset-no-major,check:new-line-citations(0 new citations),check:control-bytes,check:changeset-claims(the new changeset reads clean against itself, and no pending changeset names a touched file),check:i18n-keys,check:vi-mock-specifiers,check:test-path-roots,check-vi-mock-override-shape,check:eager-closure(3048.5 KB of 3104.5 KB),check:phantom-deps,check:esm-specifiers,check:self-import.no-explicit-anyinApiConsolePage(client: anyand thecatch (err: any)), and twoexhaustive-depsonToolPreview's existingexampleArgsuseMemo. This is a narrowed run, and three facts back it:--format json;parserOptions.projectorprojectServiceineslint.config.js), so this diff cannot move a verdict on an untouched file.Acceptance notes
{ "parameters": ... }, and the discovery catalog's/tools/:toolName/executeentry has that as its body template. The author types the body.@object-ui/console. A host that embeds app-shell without registeringdeveloper:api-consolenow lands on app-shell's "Component not registered" state for that key, not on its root.component/...nordeveloper/...is a system or metadata route. That was already the case before this change, and this change does not alter it.Findings (not fixed here; the seat files them)
AgentPreview's "Try in chat" link is the hard-coded/console/ai/agents/NAME/chat, a plain anchor. The console's root route table declares/ai/:agentand/ai/:agent/:conversationId, and nothing under/console. So the root catch-all redirects it to/, the same failure this card had. I read this at the source and did not probe it in a running app. The agent is reachable from Studio, which lists agents throughmetadata:resourcewithtype: 'agent'. Dedupe words:AgentPreview Try in chat·console/ai/agents chat link·agent preview chat link redirects home.Session:
https://claude.ai/code/session_01BP8CMtACxTdLjqR6rhd33CGenerated by Claude Code