Skip to content

feat(studio): a contributed nav entry shows the label the live app shows, and a contributor-only package says where its entries go - #30371

Merged
objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/project-thread-a2d68g
Nov 5, 2026
Merged

objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/project-thread-a2d68g

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Bill · project thread

domain:ui#3 · os-bill · session_01Utp6CbPN5UYMnBaNJPJWfG

维护者速读

  • 改了什么:Studio「界面」里别的包加进来的导航项,现在显示线上应用里看到的名字(如 Orders),id 降为下面一行灰字;只往别人应用里加导航、自己没有应用的包,不再说「还没有应用」,而是说「它向 X 的应用添加了 N 个导航项」并给打开按钮。
  • 为什么改:之前树里显示 nav_orders 这类 id,和线上应用对不上;贡献包的空状态说「没有应用」,实际它的导航出现在别的应用里,读者会误会。
  • 风险与回滚:只改 Studio 显示,不改元数据、不改服务端;名字只在 id 唯一时才取,重复 id 仍显示 id;读失败、包停用、目标应用不存在或计数为 0 时保持原文。回滚即 revert 本 PR。
  • 先看:InterfacesNavRail.tsx 的 contributedNavLabel(名字从哪来);StudioRailCustomizations.tsx 的 useContributedElsewhere(空状态发哪两次读);StudioDesignSurface.tsx 合并后的空状态分支。
  • 两分钟验证:在 app-multi-package 上打开 Studio 的 Multi-Package Core「界面」,看到 Orders 下面一行 nav_orders;再打开 Multi-Package Orders「界面」,看到「It adds 1 navigation entry to Multi-Package Core’s app.」。
  • 需要你做的:无,席位落地。

Fixes #29851

Clause-②: yes (widening) — five new @object-ui/i18n studio keys, in en and zh
Metadata-protocol: none

What changes

  • The label (part 1, amendment 6598320473). A navigation entry another package contributes to a base's app now leads with the label the live app shows, with its id as a muted second line and the package chip under it. The label is looked up by (app, id) in the app row the Interfaces pillar already reads (GET /meta/app?package=), which serves the folded navigation. servedContributedNavOf keeps only the ids the published layer does not hold and that occur exactly once in the served tree, so an id the base also holds, or one two contributions share, keeps the bare id and no label is guessed. A label-less entry reads what it inherits through the rail's own resolver (navEntryLabelText plus useNavTargetLabel). No request is added. This is a lookup by id in a served read, not the join on names ruling 6566915346 refuses.
  • The contributor's empty state (part 2, option A). A package with no app of its own whose listed manifest declares navigationContributions now says "This package has no app of its own. It adds N navigation entries to OWNER's app.", with an Open button, in the canvas and in the rail. The declared app is an address only: its owner comes from the served GET /meta/app/:app (_packageId), and the count from the owner's served GET /packages/:owner/customizations contributions, filtered by kind: nav, this packageId and that app. At most two reads per declared target app (one customization read per owner), sent only on that empty state. When the package is disabled, the target app is missing, either read fails, or the count is 0, the text stays "This package has no app yet."
  • The empty state reads the open package's declaration off the package list the Studio surface already fetched (fetchListedPackageManifests, passed down as listedManifests), so it sends no package-list request of its own.
  • Strings. engine.studio.if.noOwnApp, .noOwnAppTitle, .contributesOne, .contributesOther, .openOwner, in the studio en and zh packs. No metadata key is added.
  • Main moved under this branch. The Interfaces nav rail moved out of StudioDesignSurface.tsx into InterfacesNavRail.tsx (move-only split, [maintainer] refactor(app-shell): split StudioDesignSurface.tsx (12,882 lines) along its component seams, one move-only slice per PR #29988). The merge takes main's move and carries this card's rail hunks with it: InterfacesServedContributedNavContext, the label passed to ContributedRailRow in ContributedNavRows, and contributedNavLabel. InterfacesNavRail.tsx is outside the claim's listed file surface by name only; it holds the same code the claim named in StudioDesignSurface.tsx.

Tests

  • contributionRows.test.ts: the served entries keyed by id at any depth and never the base's own; an id the base also holds keeps no label; an id two contributions share keeps no label; no served row or another app's gives nothing; the declared target apps are addresses, each once, in order; the count of the owner's served nav contributions for the package and app.
  • StudioDesignSurface.oneSurface-28572.test.tsx, a new top-level describe in the existing harness: a unique id shows its served label with the id as a second line and a shared id keeps the bare id; CONTROL, no served fold keeps the id alone; a contributor-only package says what it adds from two reads and opens the owner; FALLBACK disabled package; FALLBACK missing target app (the owner is never read); FALLBACK failed app read or failed owner read; FALLBACK 0 entries; CONTROL, a package with no app that declares no contribution reads nothing more.

Acceptance notes

Verification

All readings on head 98ed7206f4 (main merged at 9afcd6a6b6), repository objectstack-ai/objectstack.

Type-check and tests
  • pnpm exec turbo run type-check --filter=@object-ui/app-shell --filter=@object-ui/i18n --concurrency=2: Tasks 69 successful, 69 total; lock VERDICT command-exit 0.
  • vitest (root ui/) over every test file that imports contributionRows, StudioRailCustomizations, InterfacesNavRail or StudioDesignSurface (193 files, by git grep), plus packages/i18n/: Test Files 288 passed (288); Tests 2728 passed, 13 skipped (2741); lock VERDICT command-exit 0.
  • pnpm --dir ui exec vitest run scripts/: Test Files 170 passed, 2 skipped (172); Tests 4985 passed, 2 skipped.
  • pnpm check:new-line-citations (ui): 0 new citations.
Derived gates
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 35 families from 9 changed paths (661 changed lines, under the 3000 threshold). All 35 ran and exited 0, node scripts/check-sdui-manifest.mjs included.
  • dispatch-gates --ran: 35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN.
Ablation (each leg restored, git diff HEAD empty after)
  • servedContributedNavOf forced to return nothing (node scripts/ablation-replace.mjs, anchor hit 1 to 0): 4 pins red, the three helper pins and "a unique id shows its served label with the id as a second line". Restored blob equals HEAD.
  • if (count === 0) return null; deleted from useContributedElsewhere: 2 pins red, "FALLBACK: a disabled package" and "FALLBACK: 0 entries for the declared app". Restored blob equals HEAD.
Live, en and zh, fresh boot of the merged tree

Backend examples/app-multi-package, objectstack dev --seed-admin --fresh on its own port; the Console from ui/apps/console src through Vite pointed at it; Chromium through Playwright.

  • Multi-Package Core, Interfaces, Sales group, contributed row: en "Orders / nav_orders / from Multi-Package Orders"; zh "Orders / nav_orders / 来自 Multi-Package Orders".
  • Multi-Package Orders, Interfaces, canvas: en "This package has no app of its own / It adds 1 navigation entry to Multi-Package Core’s app. / Open Multi-Package Core"; zh "这个软件包没有自己的应用 / 它向 Multi-Package Core 的应用添加了 1 个导航项。 / 打开 Multi-Package Core". The rail says the same in one line each.
  • Shots: ui-seat3/issue-29851/shots/ in the project folder (en-base-contributed-row.png, en-contributor-empty-state.png, zh-base-contributed-row.png, zh-contributor-empty-state.png).
Measured request counts
  • Reads on opening a package's Interfaces pillar, as the browser sent them. Multi-Package Core (has an app): two GET /api/v1/packages, no /meta/app/:app and no other package's customizations. Multi-Package Orders (contributor only): the same two GET /api/v1/packages, plus GET /meta/app/multi_crm and GET /packages/com.example.multi.core/customizations. The empty state adds two reads and no package-list read.
  • Platform packages on this boot (GET /api/v1/packages, 24 listed): 11 declare navigationContributions for setup. One of them is com.objectstack.setup, which owns the app and never reaches the empty state, so 10 contributor-only packages show the new sentence for setup. com.objectstack.mcp.connect-agent-ui also targets account (owner com.objectstack.account), so it sends four reads and reads "It adds 1 navigation entry to Setup’s app." and "It adds 1 navigation entry to Account’s app.". Across the 10 that is 11 targets, 22 reads in all, each sent only when that package's pillar is opened.

Generated by Claude Code

…a contributor-only package says where its entries go

The contributed nav row reads the label the live app shows, by (app, id)
in the served app row the Interfaces pillar already reads; the id stays as
a second line. A package with no app of its own that declares navigation
contributions counts its entries from the target owner's served
customization read and says so, with an Open button; any fallback keeps
the old text.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jSRtrD4WV4w6qQDYJ2
…om its double

The no-app empty state now asks the listed package manifests whether the
package contributes navigation elsewhere; this file's packages-io double
answers an empty list instead of reaching the network.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jSRtrD4WV4w6qQDYJ2
…age-list read

The no-app empty state took the open package's declared navigation targets
from a package-list request of its own. The Studio surface already reads
the listed manifests, so it now hands them to the Interfaces pillar and the
empty state sends only the owner lookup and the owner's customization read.
The Interfaces create pin's packages-io double goes back to main's.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jSRtrD4WV4w6qQDYJ2
…-a2d68g

The Interfaces nav rail moved to InterfacesNavRail.tsx on main; the
contributed-row label context and helper move with it.

Claude-Session: https://claude.ai/code/session_01Utp6Cb8MSvSrJz9nXGSj2F
Co-authored-by: Claude <noreply@anthropic.com>
…e new studio keys

Claude-Session: https://claude.ai/code/session_01Utp6Cb8MSvSrJz9nXGSj2F
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Nov 5, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#3 · os-bill · session_01Utp6CbPN5UYMnBaNJPJWfG

Known red, not this PR's · Lint UI (job 127132323590). Same signature as main-red anchor #30367: @objectstack/spec check:liveness reports query/searchFields → packages/objectql/src/engine.ts (the citation lost its key when PR #30341 moved engine.ts docblocks). This diff touches only ui/packages/app-shell (studio-design), the studio i18n packs and a changeset. The fix lands on main through #30367; this PR merges origin/main after it, then the seat re-reads CI.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#3 · os-bill · session_01Utp6CbPN5UYMnBaNJPJWfG

维护者速读

  • 改了什么:Studio「界面」里别的包加进来的导航项,显示线上应用里看到的名字(如 Orders),id 降为下面一行灰字;只往别人应用里加导航、自己没有应用的包,空状态改为「它向 X 的应用添加了 N 个导航项」并给打开按钮。
  • 为什么改:之前树里显示 nav_orders 这类 id,和线上应用对不上;贡献包的空状态说「没有应用」,读者会以为它的导航不存在(Studio: a contributed navigation entry shows its id, not its label, and a package that only contributes navigation reads "no app yet" #29851)。
  • 风险与回滚:只改 Studio 显示,不改元数据和服务端;名字只在 id 唯一时才取,读失败、包停用、目标应用不存在或计数为 0 时保持原文。回滚即撤本 PR。
  • 先看:InterfacesNavRail.tsx 的 contributedNavLabel,StudioRailCustomizations.tsx 的 useContributedElsewhere,StudioDesignSurface.tsx 的空状态分支。
  • 两分钟验证:在 app-multi-package 上打开 Studio 的 Multi-Package Core「界面」,看到 Orders 下面一行 nav_orders;再打开 Multi-Package Orders「界面」,看到「It adds 1 navigation entry to Multi-Package Core’s app.」。
  • 需要你做的:无,席位落地。
  • 席位意见:可合。

席位核对 — ACCEPT · head 5aff45c

  • 形态:9 文件 +646/−15(Studio 3 个组件、新的 contributionRows.ts 及测试、既有 one-surface 测试扩充、i18n studio 命名空间 en/zh、changeset)。check-governed-merges --pr 30371:NOT governed,661 行 ≤ 3000。Fixes #29851。
  • 契约复审(TIER_DEFAULT,席位自审):Clause-② yes (widening)。新增 5 个 @object-ui/i18n studio 键(engine.studio.if.noOwnApp、noOwnAppTitle、contributesOne、contributesOther、openOwner),只增不改不删,en/zh 成对;占位符 {count}/{package} 两语一致;无新增包导出。纯加宽,已有消费方不受影响。Metadata-protocol none。
  • 前提实测:dev 报告 6653209958(app-multi-package live 前后截图);合 main 后 head 5aff45c 上本卡两份测试 + noAppPackageLabel-12718 + emptyPackage + en/zh 与全语言键对齐共 6 文件 98 测试通过。
  • 门禁 check-runs:head 上 55 个,44 success、11 skipped(Test Core、Build Core、Build Docs、Dogfood、Temporal、opt-in 冒烟等 server/core/opt-in 项,ui-only diff 的预期跳过),无红。
  • 接受的偏差:InterfacesNavRail.tsx 在 Claim 文件面之外,原因是 main 已把界面导航栏拆到该文件([maintainer] refactor(app-shell): split StudioDesignSurface.tsx (12,882 lines) along its component seams, one move-only slice per PR #29988),本卡的改动随之移动,接受。
  • 范围外去向:报告 out_of_scope_findings 为空,无。
  • 落地条款:中继 pr_ready + automerge_enable;合并后清 pm:dispatched,在 Studio: a contributed navigation entry shows its id, not its label, and a package that only contributes navigation reads "no app yet" #29851 发落地评论。

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 5, 2026 10:08
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 5, 2026 10:08
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 5, 2026
Merged via the queue into main with commit 1e6f542 Nov 5, 2026
56 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-a2d68g branch November 5, 2026 10:26
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/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Studio: a contributed navigation entry shows its id, not its label, and a package that only contributes navigation reads "no app yet"

1 participant