You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
console Home: the "Your apps" tiles are buttons, not links — an app cannot be opened in a new tab (middle-click, ⌘/Ctrl-click, "Open link in new tab") and its address cannot be copied #12305
Filing-gate category ③ — the maintainer's order in PM session session_016JB93R2KcQP4GgKaUnseVF, verbatim: 「按这 5 类开父单,派给 objectui 车道,以及:820px 宽度下面包屑重叠。* 每个新库首屏都弹时区设置。* Home 页的应用卡片不是链接,没法在新标签页打开。」
Reader who acts: the objectui domain:ui execution seat. Dedupe: semantic issue search on this repo, closed included, "home apps strip tiles are buttons not links cannot open in new tab middle click" — 0 hits.
What happens
On /_console/home, each app in the "Your apps" strip is a button with an onClick that navigates in place. So the browser's link affordances are absent: middle-click and ⌘/Ctrl-click do nothing extra, there is no "Open link in new tab" / "Copy link address" in the context menu, and hovering shows no destination. The browser-pass runners had to open apps by typing their URL (/_console/apps/showcase_app).
Where (objectui 1fb1ced36243)
packages/app-shell/src/console/home/HomeAppsStrip.tsx — the tile is <button type="button" onClick={() => onOpen(app)}> (around line 131).
Expected
Each tile is a real link to the app's route (the same address onOpen navigates to), keeping its look, so the platform's normal link behaviours work; a plain left-click still navigates in place.
Claim: PM loop round 5, next slot. The direction is the card's own: the maintainer's order it quotes (「Home 页的应用卡片不是链接,没法在新标签页打开。」) and its "Expected".
Each "Your apps" tile is a real link to the app's route, the same address onOpen navigates to today. It keeps its look.
The browser's link affordances work: middle-click, ⌘/Ctrl-click, "Open link in new tab", "Copy link address", and the hover destination.
A plain left-click still navigates in place.
Session: session_01CsyMRsFJBKNEMa4oPY1b5z
Account: zhuangjianguo (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-12305-home-app-tiles-are-links
Worktree: objectui-issue-12305
Domain: domain:ui
Seat: domain:ui#1
File surface: the Home apps strip and its two mounts, read on objectui main093d55b at 2026-10-13T07:07Z. Stop on a breach and explain it in the report.
The strip:packages/app-shell/src/console/home/HomeAppsStrip.tsx. Each tile is <button type="button" onClick={() => onOpen(app)}>.
Its two mounts: both pass onOpen as a navigate() to /apps/${appRouteSegment(app) ?? app.name}.
packages/app-shell/src/console/home/HomePage.tsx.
packages/app-shell/src/views/app-launcher-renderer.tsx, the app-launcher block.
Co-located tests and a changeset.
⛔ The "More" / "Show less" controls and the marketplace shortcut stay buttons, because they navigate nowhere addressable. ⛔ No new export, spec key or i18n key unless the report justifies it.
Container & model: S, mode:subagent, model: default tier — dispatch-gates --tier --repo objectstack-ai/objectui: "no path-derived mandate"
Clause-②: no
Clause-② reason:
HomeAppsStrip is not exported from @object-ui/app-shell's entry, so its props are internal.
The app-launcher block's schema does not move.
No published type, export or accepted input moves, and none of the three contract surfaces is touched.
Responsibility: app-shell's HomeAppsStrip (tiles rendered as buttons with an in-place navigate) | none: react-router's Link already gives an in-place click plus the browser's link affordances | every console user on Home, and every page that places the app-launcher block, opening an app in a new tab or copying its address (reported on the card by the browser-pass runners)
Thread-read: none
Serial constraints cleared: read at 2026-10-13T07:07Z on objectui main093d55b.
This seat's in-flight work shares no file: objectui#12309 (in the merge queue), objectui#12313 (AppManagementPage.tsx, data-objectstack, i18n packs) and the objectui#12295 dev (data-objectstack, layout/AppHeader.tsx, layout/ConnectionStatus.tsx).
os-dev-report
{
"issue": 12305,
"status": "done",
"branch": "claude/issue-12305-home-app-tiles-are-links",
"pr": "#12319",
"session": "session_01CsyMRsFJBKNEMa4oPY1b5z — this run's harness-stamped Claude-Session id (a dispatched subagent, so the parent seat's id)",
"premise_still_valid": true,
"summary": "Premise held at ca45c6d. Each tile was a button whose onClick called onOpen, and both mounts (HomePage and the app:launcher block) passed a navigate() to the same /apps/SEGMENT rule. HomeAppsStrip now renders each app tile as a react-router Link to /apps/SEGMENT (ADR-0048: package id, else name), keeping its classes and data-testid. The strip owns the address rule, so the onOpen prop and both mounts' copies of it are gone; the app:launcher block no longer needs useNavigate. A plain left-click navigates in place, a modified or middle click is left to the browser, href carries the basename (/_console/apps/SEGMENT), and More / Show less / the marketplace shortcut stay buttons. Seven HomePage harnesses whose react-router-dom stand-in exported only useNavigate gained a Link stand-in (fixture triage, measured), and a changeset declares an app-shell patch. Draft PR #12319 is open; CI was in_progress at report time.",
"tests": "All at HEAD 26e52e0 (BASE ca45c6d). (1) Through os-verify-lock: pnpm exec vitest run packages/app-shell/src/console/home/ + views/tests/phase1-page-blocks.render.test.tsx + views/metadata-admin/previews/tests/exclusion-reason-truthfulness.test.ts: first run before the triage 'Test Files 7 failed | 16 passed (23) / Tests 44 failed | 99 passed (143)', every failure 'No "Link" export is defined on the "react-router-dom" mock'; after the triage 'Test Files 23 passed (23) / Tests 143 passed (143)', VERDICT command-exit 0. The new HomeAppsStrip.tilesAreLinks-12305 cases pass: link role, href /_console/apps/crm and /_console/apps/com.acme.ops, plain click cancelled and the router moves to /apps/com.acme.ops, Cmd/Ctrl/Shift/Alt/middle clicks not cancelled and the router stays put, More / Show less / marketplace are BUTTONs. (2) Router-mocking suites that can reach a tile, enumerated (every test mocking react-router-dom that names the app-shell entry, HomePage, the strip, the launcher or a registry sweep) plus utils/tests/appRoute.test.ts: 'Test Files 6 passed (6) / Tests 110 passed (110)'. (3) pnpm --filter @object-ui/app-shell type-check echoed 'tsc --noEmit && tsc -p tsconfig.test.json', VERDICT command-exit 0; tsc -p tsconfig.test.json --listFilesOnly lists all 12 touched TS files. (4) eslint --no-inline-config --format json on the 12 touched files (all isPathIgnored false per the ESLint API; type-aware linting off; no custom rule reads the filesystem, so untouched files' verdicts cannot move): 'files: 12 errors: 0 warnings: 17' (all pre-existing no-explicit-any); the same files at BASE gave 19 warnings. (5) Ablation leg 1, the three source files checked out at BASE (button + onOpen wiring), with on-disk proof: Link-tag count 1 → 0, onOpen-button text 0 → 1, each file's hash equal to its BASE blob. Result 'Tests 4 failed | 11 passed (15)': link/href, plain click, buttons-stay, launcher href. NOT discriminating for the 5 modified-click cases, because the BASE button calls an onOpen the pin never passes. Ablation leg 2, the tile as a button that navigates (the true pre-fix behaviour), via objectstack scripts/ablation-replace.mjs in five anchored edits, each 'mutation landed: anchor 1 → 0' with the blob changed: 'Tests 9 failed | 6 passed (15)', all 8 strip cases plus the launcher href. Both legs restored: blob == HEAD and git diff HEAD empty (trap plus hash proof). A first attempt at leg 2 was refused by the tool (the step-2 replacement re-contained its anchor, 'anchor count moved 1 → 1'), the tool restored to HEAD, and no test ran on it. (6) Visual fence, measured in Chromium (/opt/pw-browsers/chromium) with the built console stylesheet, the BASE button tile beside the a tile, same classes and inner markup, light and dark: geometry equal (tile 140.5x92, icon and label boxes equal); hover screenshots byte-identical; rest screenshots differ on 176/12972 (light) and 174/12972 (dark) pixels with maxChannelDelta 1 (rasterisation noise; a one-character label change registers); computed-style deltas are only cursor default → pointer, plus the transition and press rules from the console theme's unlayered button:not([disabled]) rule (see open_questions). (7) Builds: turbo build --filter=@object-ui/app-shell^... 'Tasks: 28 successful, 28 total'; pnpm --filter @object-ui/app-shell build exit 0, and dist/index.d.ts names HomeAppsStrip 0 times, AppLauncherRenderer 0 times and the control HomePage 1 time; apps/console vite build exit 0 for the bundle gates. Declared to CI, not run locally: the full @object-ui/app-shell suite and the repo-wide pnpm lint.",
"gates": [
{ "command": "node scripts/check-changeset-presence.mjs", "exit": 0, "verdict": "12 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/12305-home-app-tiles-are-links.md." },
{ "command": "pnpm check:changeset-claims", "exit": 0, "verdict": "report-only; 12 file(s) changed outside .changeset/, read against 407 pending declaration(s); no finding printed" },
{ "command": "pnpm check:pending-changeset-literals", "exit": 0, "verdict": "No test source names a pending changeset." },
{ "command": "pnpm check:new-line-citations", "exit": 0, "verdict": "VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only → exit 0" },
{ "command": "pnpm check:control-bytes", "exit": 0, "verdict": "check-control-bytes: OK (scanned 8597 tracked text file(s); skipped 85 binary)." },
{ "command": "pnpm check:test-path-roots", "exit": 0, "verdict": "check-test-path-roots: OK" },
{ "command": "pnpm check:vi-mock-specifiers", "exit": 0, "verdict": "check-vi-mock-specifiers: OK" },
{ "command": "pnpm check:vi-mock-inherit", "exit": 0, "verdict": "check-vi-mock-inherit: OK (react-router-dom is third-party: counted, never judged)" },
{ "command": "pnpm check:vi-mock-override-shape", "exit": 0, "verdict": "check-vi-mock-override-shape: OK" },
{ "command": "node scripts/check-changeset-no-major.mjs", "exit": 0, "verdict": "No changeset declares a major bump." },
{ "command": "node scripts/check-changeset-fixed.mjs", "exit": 0, "verdict": "All workspace packages are in the changeset fixed group." },
{ "command": "node scripts/check-changeset-overwrite.mjs", "exit": 0, "verdict": "1 changeset(s) added, 0 modified, 0 deleted. No pre-existing changeset was modified or deleted." },
{ "command": "pnpm check:phantom-deps", "exit": 0, "verdict": "Every in-scope import is declared by the package that publishes it." },
{ "command": "pnpm check:sdui-registration-pins", "exit": 0, "verdict": "All 15 registration(s) a sideEffects array promises are present in the built console (2255 chunks weighed), launcher included. A first run exited 2 = PREREQUISITE NOT MET (no console build), not a measurement; it was re-run after vite build." },
{ "command": "pnpm check:eager-closure", "exit": 0, "verdict": "Console eager closure is 3132.6 KB gzipped across 72 of 2255 chunks (budget: 3204.6 KB)." },
{ "command": "pnpm --filter @object-ui/app-shell type-check", "exit": 0, "verdict": "tsc --noEmit && tsc -p tsconfig.test.json, VERDICT command-exit 0" },
{ "command": "eslint --no-inline-config (12 touched files)", "exit": 0, "verdict": "0 errors, 17 warnings (BASE: 19)" },
{ "command": "check:i18n-", "exit": null, "verdict": "not run: no string, t() call or i18n key added" }
],
"line_budget": { "applies": false, "reason": "No skills/** or ledgered governed file touched; no managed line ceiling is in play.", "diff": "13 files changed, 263 insertions(+), 41 deletions(-) vs BASE ca45c6d" },
"files_changed": [
".changeset/12305-home-app-tiles-are-links.md +9 -0",
"packages/app-shell/src/console/home/HomeAppsStrip.tsx +17 -7",
"packages/app-shell/src/console/home/HomePage.tsx +0 -2",
"packages/app-shell/src/views/app-launcher-renderer.tsx +2 -10",
"packages/app-shell/src/console/home/tests/HomeAppsStrip.tilesAreLinks-12305.test.tsx +154 -0",
"packages/app-shell/src/views/tests/phase1-page-blocks.render.test.tsx +3 -0",
"packages/app-shell/src/console/home/tests/HomePage.aiStudioDisabled.test.tsx +11 -3",
"packages/app-shell/src/console/home/tests/HomePage.approvalsTarget.test.tsx +11 -3",
"packages/app-shell/src/console/home/tests/HomePage.authoringCapabilityGate.test.tsx +11 -3",
"packages/app-shell/src/console/home/tests/HomePage.greetingLocale-11689.test.tsx +11 -3",
"packages/app-shell/src/console/home/tests/HomePage.inboxLinksTarget.test.tsx +12 -4",
"packages/app-shell/src/console/home/tests/HomePage.marketplaceDisabled.test.tsx +11 -3",
"packages/app-shell/src/console/home/tests/HomePage.notificationDeepLink.test.tsx +11 -3"
],
"deviations": [
"Zone 2 #4 lead corrected: AppSwitcher.appIdentity-12201.test.tsx matches only the --os-app-tile-bg CSS variable and does not render the strip, so it was not touched. The measured triage set is seven HomePage suites (aiStudioDisabled, approvalsTarget, authoringCapabilityGate, greetingLocale-11689, inboxLinksTarget, marketplaceDisabled, notificationDeepLink). registry-inputs-spec-parity does not import app-shell and needed nothing.",
"Ablation run as two legs, not one. The suggested 'restore the button' leg (BASE sources) could not make the five modified-click cases fail, because the BASE button calls an onOpen the pin never passes. A second leg (a button that navigates) was added; it turned every strip case red.",
"Bundle gates were fed by vite build alone in apps/console, not the full console build script (tsc && vite build && sdui-manifest && plugin); both gates read only apps/console/dist/assets.",
"Narrowing declared: the full @object-ui/app-shell vitest suite and repo-wide pnpm lint were not run locally; CI owns both. The suites that can render the strip were enumerated and run (sections 1-2 of tests).",
"Branch not synced: origin/main moved 2 commits after the cut (to 068a540), none touching these files; the merge queue rebuilds on main, so HEAD stays at 26e52e0, where every gate above ran.",
"Attribution: this session's harness reminder asked for a commit trailer naming a model and a different PR footer. Per objectui AGENTS.md (the model-free trailer pair) and the os-dev role file (session-URL PR footer), commits carry the model-free Claude-Session and Co-authored-by pair, and the PR body ends in the session-URL footer.",
"No label written (fenced). No MCP call."
],
"mcp_calls": "0 — none",
"api_writes": "2 — both via the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): (1) pr_create = POST /repos/objectstack-ai/objectui/pulls (draft) + POST /repos//issues/12319/assignees [zhuangjianguo]; read back 7681 bytes sent / 7681 stored, identical; (2) this os-dev-report comment = POST /repos//issues/12305/comments. Plus 3 git pushes (not REST).",
"open_questions": [
{
"question": "Press feel of the app tiles. The console theme's unlayered button:not([disabled]) rule ('iOS-style press springback' in apps/console/src/index.css) applies to buttons only. On the old button tile it added transform scale(.96) over a 180 ms springback curve on top of the tile's own active:scale-[0.96], which Tailwind v4 writes as the separate scale property, so a held tile shrank to 0.9216. The new link tile presses with its own classes: scale 0.96 over the 150 ms transition utility, and its hover background now fades over 150 ms where it used to snap. The cursor also becomes the pointer hand. At rest and on hover the tile is pixel-identical. Is that press and hover delta acceptable under the 'no visual change' fence?",
"options": [
"A — Accept as shipped: the link tile behaves as its own classes declare. Cost: none; a 0.96 press in place of the accidental 0.9216, and a 150 ms hover fade.",
"B — Widen the console theme's press rule (apps/console/src/index.css) to also match these anchors, recreating the compound 0.9216 press and the springback. Cost: a theme edit outside this card's file surface, and it re-encodes an accidental compound of two scale rules.",
"C — Hard-code the springback and an extra transform on the tile in app-shell. Cost: couples a published package to one app's optional theme block, which says it can be deleted to revert to the stock look."
],
"recommendation": "A. Real need: the maintainer asked for working link behaviour with the look kept, and the static look is pixel-identical; only the transient press depth and the hover fade moved. Long-term: A leaves app-shell free of console-theme knowledge, while B and C entrench a double-scale that no one declared. AI-error resistance: one declared press rule on the component is harder to get wrong than a theme selector that must track which elements are tiles. Startup focus: A costs nothing; B and C add surface for a sub-200 ms transient."
}
],
"out_of_scope_findings": [
"carrier: none (承接者:无) · noted, not filed — the console theme's unlayered button:not([disabled]):active{transform:scale(.96)} compounds with any Tailwind active:scale- on a button (measured on the BASE tile: transform scale(.96) plus scale .96 = 0.9216 held); the 'More' tile beside the app tiles still does this. Polish, no class a/b/c. dedupe words: active scale compound, button press springback, iOS theme transform",
"carrier: none (承接者:无) · noted, not filed — packages/app-shell/src/console/home/AppCard.tsx opens an app through an overlay button (same shape as this card), but nothing in the tree renders AppCard (only comments name it): dead code. dedupe words: AppCard unused, app-card-open button",
"carrier: the objectui#12295 claim (owns layout/AppHeader.tsx) · noted, not filed — the header's hidden-apps DropdownMenuItems and the AppSwitcher items open apps via onClick navigate; menu-item idiom; read-only inference, not measured in a browser. dedupe words: hidden apps menu link, app switcher open in new tab"
]
}
Merged through the merge queue at 2026-10-13T08:19Z. This card closed completed by the PR's Fixes #12305. No other lane card closed at the merge.
Landed by content, not by sha: all 13 of the PR's files on main0ffa076 are blob-identical to the accepted head 26e52e0 (ACCEPT 6141116132).
pm:dispatched was removed and read back. domain:ui, area:records, the type and the priority stay.
The changeset rides the next objectui release: @object-ui/app-shellpatch.
What is on main:
Each "Your apps" tile on Home, and in the app:launcher page block, is a link to /_console/apps/SEGMENT, where SEGMENT is the app's package id, else its name.
Middle-click, ⌘/Ctrl-click, "Open link in new tab", "Copy link address" and the hover destination all work.
A plain left click still navigates in place.
"More", "Show less" and the marketplace shortcut stay buttons.
Activity
objectstack-fleet commentedon Oct 13, 2026
Claim: PM loop round 5, next slot. The direction is the card's own: the maintainer's order it quotes (「Home 页的应用卡片不是链接,没法在新标签页打开。」) and its "Expected".
onOpennavigates to today. It keeps its look.Session:
session_01CsyMRsFJBKNEMa4oPY1b5zAccount:
zhuangjianguo(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-12305-home-app-tiles-are-linksWorktree:
objectui-issue-12305Domain:
domain:uiSeat:
domain:ui#1File surface: the Home apps strip and its two mounts, read on objectui
main093d55bat 2026-10-13T07:07Z. Stop on a breach and explain it in the report.packages/app-shell/src/console/home/HomeAppsStrip.tsx. Each tile is<button type="button" onClick={() => onOpen(app)}>.onOpenas anavigate()to/apps/${appRouteSegment(app) ?? app.name}.packages/app-shell/src/console/home/HomePage.tsx.packages/app-shell/src/views/app-launcher-renderer.tsx, theapp-launcherblock.Container & model:
S,mode:subagent,model: default tier — dispatch-gates --tier --repo objectstack-ai/objectui: "no path-derived mandate"Clause-②: no
Clause-② reason:
HomeAppsStripis not exported from@object-ui/app-shell's entry, so its props are internal.app-launcherblock's schema does not move.Responsibility:
app-shell's HomeAppsStrip (tiles rendered as buttons with an in-place navigate) | none: react-router's Link already gives an in-place click plus the browser's link affordances | every console user on Home, and every page that places the app-launcher block, opening an app in a new tab or copying its address (reported on the card by the browser-pass runners)Thread-read: none
Serial constraints cleared: read at 2026-10-13T07:07Z on objectui
main093d55b.packages/app-shell/src/console/home/**orviews/app-launcher-renderer.tsx(objectui#12313, fix(plugin-grid,plugin-list,components,types): the grid's inline-edit saves send each row's If-Match, and Save All reports per row (objectui#12292) #12309, chore(deps): bump next from 16.3.6 to 16.3.8 #11845, build(deps): Bump next from 16.3.6 to 16.3.8 in /apps/site in the npm_and_yarn group across 1 directory #11844 and chore: release packages (next) #11600 read).AppManagementPage.tsx,data-objectstack, i18n packs) and the objectui#12295 dev (data-objectstack,layout/AppHeader.tsx,layout/ConnectionStatus.tsx).Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
os-dev-report
{
"issue": 12305,
"status": "done",
"branch": "claude/issue-12305-home-app-tiles-are-links",
"pr": "#12319",
"session": "session_01CsyMRsFJBKNEMa4oPY1b5z — this run's harness-stamped Claude-Session id (a dispatched subagent, so the parent seat's id)",
"premise_still_valid": true,
"summary": "Premise held at ca45c6d. Each tile was a button whose onClick called onOpen, and both mounts (HomePage and the app:launcher block) passed a navigate() to the same /apps/SEGMENT rule. HomeAppsStrip now renders each app tile as a react-router Link to /apps/SEGMENT (ADR-0048: package id, else name), keeping its classes and data-testid. The strip owns the address rule, so the onOpen prop and both mounts' copies of it are gone; the app:launcher block no longer needs useNavigate. A plain left-click navigates in place, a modified or middle click is left to the browser, href carries the basename (/_console/apps/SEGMENT), and More / Show less / the marketplace shortcut stay buttons. Seven HomePage harnesses whose react-router-dom stand-in exported only useNavigate gained a Link stand-in (fixture triage, measured), and a changeset declares an app-shell patch. Draft PR #12319 is open; CI was in_progress at report time.",
"tests": "All at HEAD 26e52e0 (BASE ca45c6d). (1) Through os-verify-lock: pnpm exec vitest run packages/app-shell/src/console/home/ + views/tests/phase1-page-blocks.render.test.tsx + views/metadata-admin/previews/tests/exclusion-reason-truthfulness.test.ts: first run before the triage 'Test Files 7 failed | 16 passed (23) / Tests 44 failed | 99 passed (143)', every failure 'No "Link" export is defined on the "react-router-dom" mock'; after the triage 'Test Files 23 passed (23) / Tests 143 passed (143)', VERDICT command-exit 0. The new HomeAppsStrip.tilesAreLinks-12305 cases pass: link role, href /_console/apps/crm and /_console/apps/com.acme.ops, plain click cancelled and the router moves to /apps/com.acme.ops, Cmd/Ctrl/Shift/Alt/middle clicks not cancelled and the router stays put, More / Show less / marketplace are BUTTONs. (2) Router-mocking suites that can reach a tile, enumerated (every test mocking react-router-dom that names the app-shell entry, HomePage, the strip, the launcher or a registry sweep) plus utils/tests/appRoute.test.ts: 'Test Files 6 passed (6) / Tests 110 passed (110)'. (3) pnpm --filter @object-ui/app-shell type-check echoed 'tsc --noEmit && tsc -p tsconfig.test.json', VERDICT command-exit 0; tsc -p tsconfig.test.json --listFilesOnly lists all 12 touched TS files. (4) eslint --no-inline-config --format json on the 12 touched files (all isPathIgnored false per the ESLint API; type-aware linting off; no custom rule reads the filesystem, so untouched files' verdicts cannot move): 'files: 12 errors: 0 warnings: 17' (all pre-existing no-explicit-any); the same files at BASE gave 19 warnings. (5) Ablation leg 1, the three source files checked out at BASE (button + onOpen wiring), with on-disk proof: Link-tag count 1 → 0, onOpen-button text 0 → 1, each file's hash equal to its BASE blob. Result 'Tests 4 failed | 11 passed (15)': link/href, plain click, buttons-stay, launcher href. NOT discriminating for the 5 modified-click cases, because the BASE button calls an onOpen the pin never passes. Ablation leg 2, the tile as a button that navigates (the true pre-fix behaviour), via objectstack scripts/ablation-replace.mjs in five anchored edits, each 'mutation landed: anchor 1 → 0' with the blob changed: 'Tests 9 failed | 6 passed (15)', all 8 strip cases plus the launcher href. Both legs restored: blob == HEAD and git diff HEAD empty (trap plus hash proof). A first attempt at leg 2 was refused by the tool (the step-2 replacement re-contained its anchor, 'anchor count moved 1 → 1'), the tool restored to HEAD, and no test ran on it. (6) Visual fence, measured in Chromium (/opt/pw-browsers/chromium) with the built console stylesheet, the BASE button tile beside the a tile, same classes and inner markup, light and dark: geometry equal (tile 140.5x92, icon and label boxes equal); hover screenshots byte-identical; rest screenshots differ on 176/12972 (light) and 174/12972 (dark) pixels with maxChannelDelta 1 (rasterisation noise; a one-character label change registers); computed-style deltas are only cursor default → pointer, plus the transition and press rules from the console theme's unlayered button:not([disabled]) rule (see open_questions). (7) Builds: turbo build --filter=@object-ui/app-shell^... 'Tasks: 28 successful, 28 total'; pnpm --filter @object-ui/app-shell build exit 0, and dist/index.d.ts names HomeAppsStrip 0 times, AppLauncherRenderer 0 times and the control HomePage 1 time; apps/console vite build exit 0 for the bundle gates. Declared to CI, not run locally: the full @object-ui/app-shell suite and the repo-wide pnpm lint.",
"gates": [
{ "command": "node scripts/check-changeset-presence.mjs", "exit": 0, "verdict": "12 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/12305-home-app-tiles-are-links.md." },
{ "command": "pnpm check:changeset-claims", "exit": 0, "verdict": "report-only; 12 file(s) changed outside .changeset/, read against 407 pending declaration(s); no finding printed" },
{ "command": "pnpm check:pending-changeset-literals", "exit": 0, "verdict": "No test source names a pending changeset." },
{ "command": "pnpm check:new-line-citations", "exit": 0, "verdict": "VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only → exit 0" },
{ "command": "pnpm check:control-bytes", "exit": 0, "verdict": "check-control-bytes: OK (scanned 8597 tracked text file(s); skipped 85 binary)." },
{ "command": "pnpm check:test-path-roots", "exit": 0, "verdict": "check-test-path-roots: OK" },
{ "command": "pnpm check:vi-mock-specifiers", "exit": 0, "verdict": "check-vi-mock-specifiers: OK" },
{ "command": "pnpm check:vi-mock-inherit", "exit": 0, "verdict": "check-vi-mock-inherit: OK (react-router-dom is third-party: counted, never judged)" },
{ "command": "pnpm check:vi-mock-override-shape", "exit": 0, "verdict": "check-vi-mock-override-shape: OK" },
{ "command": "node scripts/check-changeset-no-major.mjs", "exit": 0, "verdict": "No changeset declares a
majorbump." },{ "command": "node scripts/check-changeset-fixed.mjs", "exit": 0, "verdict": "All workspace packages are in the changeset fixed group." },
{ "command": "node scripts/check-changeset-overwrite.mjs", "exit": 0, "verdict": "1 changeset(s) added, 0 modified, 0 deleted. No pre-existing changeset was modified or deleted." },
{ "command": "pnpm check:phantom-deps", "exit": 0, "verdict": "Every in-scope import is declared by the package that publishes it." },
{ "command": "pnpm check:sdui-registration-pins", "exit": 0, "verdict": "All 15 registration(s) a
sideEffectsarray promises are present in the built console (2255 chunks weighed), launcher included. A first run exited 2 = PREREQUISITE NOT MET (no console build), not a measurement; it was re-run after vite build." },{ "command": "pnpm check:eager-closure", "exit": 0, "verdict": "Console eager closure is 3132.6 KB gzipped across 72 of 2255 chunks (budget: 3204.6 KB)." },
{ "command": "pnpm --filter @object-ui/app-shell type-check", "exit": 0, "verdict": "tsc --noEmit && tsc -p tsconfig.test.json, VERDICT command-exit 0" },
{ "command": "eslint --no-inline-config (12 touched files)", "exit": 0, "verdict": "0 errors, 17 warnings (BASE: 19)" },
{ "command": "check:i18n-", "exit": null, "verdict": "not run: no string, t() call or i18n key added" }
],
"line_budget": { "applies": false, "reason": "No skills/** or ledgered governed file touched; no managed line ceiling is in play.", "diff": "13 files changed, 263 insertions(+), 41 deletions(-) vs BASE ca45c6d" },
"files_changed": [
".changeset/12305-home-app-tiles-are-links.md +9 -0",
"packages/app-shell/src/console/home/HomeAppsStrip.tsx +17 -7",
"packages/app-shell/src/console/home/HomePage.tsx +0 -2",
"packages/app-shell/src/views/app-launcher-renderer.tsx +2 -10",
"packages/app-shell/src/console/home/tests/HomeAppsStrip.tilesAreLinks-12305.test.tsx +154 -0",
"packages/app-shell/src/views/tests/phase1-page-blocks.render.test.tsx +3 -0",
"packages/app-shell/src/console/home/tests/HomePage.aiStudioDisabled.test.tsx +11 -3",
"packages/app-shell/src/console/home/tests/HomePage.approvalsTarget.test.tsx +11 -3",
"packages/app-shell/src/console/home/tests/HomePage.authoringCapabilityGate.test.tsx +11 -3",
"packages/app-shell/src/console/home/tests/HomePage.greetingLocale-11689.test.tsx +11 -3",
"packages/app-shell/src/console/home/tests/HomePage.inboxLinksTarget.test.tsx +12 -4",
"packages/app-shell/src/console/home/tests/HomePage.marketplaceDisabled.test.tsx +11 -3",
"packages/app-shell/src/console/home/tests/HomePage.notificationDeepLink.test.tsx +11 -3"
],
"deviations": [
"Zone 2 #4 lead corrected: AppSwitcher.appIdentity-12201.test.tsx matches only the --os-app-tile-bg CSS variable and does not render the strip, so it was not touched. The measured triage set is seven HomePage suites (aiStudioDisabled, approvalsTarget, authoringCapabilityGate, greetingLocale-11689, inboxLinksTarget, marketplaceDisabled, notificationDeepLink). registry-inputs-spec-parity does not import app-shell and needed nothing.",
"Ablation run as two legs, not one. The suggested 'restore the button' leg (BASE sources) could not make the five modified-click cases fail, because the BASE button calls an onOpen the pin never passes. A second leg (a button that navigates) was added; it turned every strip case red.",
"Bundle gates were fed by
vite buildalone in apps/console, not the full console build script (tsc && vite build && sdui-manifest && plugin); both gates read only apps/console/dist/assets.","Narrowing declared: the full @object-ui/app-shell vitest suite and repo-wide pnpm lint were not run locally; CI owns both. The suites that can render the strip were enumerated and run (sections 1-2 of tests).",
"Branch not synced: origin/main moved 2 commits after the cut (to 068a540), none touching these files; the merge queue rebuilds on main, so HEAD stays at 26e52e0, where every gate above ran.",
"Attribution: this session's harness reminder asked for a commit trailer naming a model and a different PR footer. Per objectui AGENTS.md (the model-free trailer pair) and the os-dev role file (session-URL PR footer), commits carry the model-free Claude-Session and Co-authored-by pair, and the PR body ends in the session-URL footer.",
"No label written (fenced). No MCP call."
],
"mcp_calls": "0 — none",
"api_writes": "2 — both via the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): (1) pr_create = POST /repos/objectstack-ai/objectui/pulls (draft) + POST /repos//issues/12319/assignees [zhuangjianguo]; read back 7681 bytes sent / 7681 stored, identical; (2) this os-dev-report comment = POST /repos//issues/12305/comments. Plus 3 git pushes (not REST).",
"open_questions": [
{
"question": "Press feel of the app tiles. The console theme's unlayered
button:not([disabled])rule ('iOS-style press springback' in apps/console/src/index.css) applies to buttons only. On the old button tile it added transform scale(.96) over a 180 ms springback curve on top of the tile's own active:scale-[0.96], which Tailwind v4 writes as the separatescaleproperty, so a held tile shrank to 0.9216. The new link tile presses with its own classes: scale 0.96 over the 150 mstransitionutility, and its hover background now fades over 150 ms where it used to snap. The cursor also becomes the pointer hand. At rest and on hover the tile is pixel-identical. Is that press and hover delta acceptable under the 'no visual change' fence?","options": [
"A — Accept as shipped: the link tile behaves as its own classes declare. Cost: none; a 0.96 press in place of the accidental 0.9216, and a 150 ms hover fade.",
"B — Widen the console theme's press rule (apps/console/src/index.css) to also match these anchors, recreating the compound 0.9216 press and the springback. Cost: a theme edit outside this card's file surface, and it re-encodes an accidental compound of two scale rules.",
"C — Hard-code the springback and an extra transform on the tile in app-shell. Cost: couples a published package to one app's optional theme block, which says it can be deleted to revert to the stock look."
],
"recommendation": "A. Real need: the maintainer asked for working link behaviour with the look kept, and the static look is pixel-identical; only the transient press depth and the hover fade moved. Long-term: A leaves app-shell free of console-theme knowledge, while B and C entrench a double-scale that no one declared. AI-error resistance: one declared press rule on the component is harder to get wrong than a theme selector that must track which elements are tiles. Startup focus: A costs nothing; B and C add surface for a sub-200 ms transient."
}
],
"out_of_scope_findings": [
"carrier: none (承接者:无) · noted, not filed — the console theme's unlayered button:not([disabled]):active{transform:scale(.96)} compounds with any Tailwind active:scale- on a button (measured on the BASE tile: transform scale(.96) plus scale .96 = 0.9216 held); the 'More' tile beside the app tiles still does this. Polish, no class a/b/c. dedupe words: active scale compound, button press springback, iOS theme transform",
"carrier: none (承接者:无) · noted, not filed — packages/app-shell/src/console/home/AppCard.tsx opens an app through an overlay button (same shape as this card), but nothing in the tree renders AppCard (only comments name it): dead code. dedupe words: AppCard unused, app-card-open button",
"carrier: the objectui#12295 claim (owns layout/AppHeader.tsx) · noted, not filed — the header's hidden-apps DropdownMenuItems and the AppSwitcher items open apps via onClick navigate; menu-item idiom; read-only inference, not measured in a browser. dedupe words: hidden apps menu link, app switcher open in new tab"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
Landed: objectui PR #12319 →
main0ffa076·domain:uiseat 1 ·session_01CsyMRsFJBKNEMa4oPY1b5z· 2026-10-13T08:20Zcompletedby the PR'sFixes #12305. No other lane card closed at the merge.main0ffa076are blob-identical to the accepted head26e52e0(ACCEPT6141116132).pm:dispatchedwas removed and read back.domain:ui,area:records, the type and the priority stay.@object-ui/app-shellpatch.main:app:launcherpage block, is a link to/_console/apps/SEGMENT, where SEGMENT is the app's package id, else its name.Generated by Claude Code