Skip to content

spec / objectui navigation: an app's navigation item cannot read data, so a label or a visible gate cannot follow a live count (the control plane's 「环境」 for a single-environment organization) #24332

Closed

Description

@objectstack-fleet

Ruled: 6259393328 · letters Q1 E · Q2 A · 2026-10-19T04:04Z

Filed by the repo:cloud#2 seat (seat post #24145, session session_01Cbdek8SKSHxddGqTPZXdqX) from the objectstack-ai/cloud#3002 dev report (PR objectstack-ai/cloud#3019): the one place of that ruling's scope the console cannot express at the pins. ⛔ Not a claim.

Pull: a ruled consumer

  • The ruling: objectstack-ai/cloud#3002 letter A (6248811507, the maintainer's 「3002 A」): keep 「环境」 out of a single-environment organization's way. Where one environment exists, its name stands in for the noun. Its inventory's row 1 is the sidebar item: 「单环境组织:显示环境名称,点击直达详情;多环境:保留「环境」」 (6247161029).
  • The maintainer's 口径 on that ruling: 「平台相关的功能,应该尽量先改平台」.
  • The consumer: cloud's control-plane app, navigation item nav_environments (packages/service-tenant/src/translations/zh-CN.ts:39). Every other place in that inventory row set is done in PR objectstack-ai/cloud#3019, or waits on the environments app page (objectstack-ai/cloud#2916).

What was measured (framework pin be076dad28a0, objectui 0707b4ae)

  • The label is static. packages/spec/src/ui/app.zod.ts (~L332): a navigation item's label is an I18nLabel: a bundle entry, an inline locale map, or the target's label inherited at render. Nothing in it reads data.
  • The gate has no data root. objectui packages/app-shell/src/hooks/useNavMemberGates.ts (~L80–84) evaluates a navigation item's visible with no data in scope.
  • The renderer resolves the label once. objectui packages/layout/src/NavigationRenderer.tsx (~L490), resolveNavItemLabel, returns a fixed string.
  • A page element can read data. ElementApiDataSourceSchema lets a page subtree read one declared API and gate on data.*, which is how cloud's home now hides the environment count for one environment.

Wanted (the shape is the spec seat's call)

One of:

  1. A navigation item may declare one API data source, as a page element does, and its label (a ${…} hole) and visible read data.* from it.
  2. A predicate root that carries the active organization's live environment count, and at a count of 1 its name, for label and visible.

Either way, the declaration is refused at publish where the runtime cannot honour it, never silently dropped.

Acceptance

  • The spec declares the chosen shape, and objectui honours it in the sidebar, with a test each.
  • objectstack-ai/cloud#3002's sidebar row can then be done in cloud with the next pin move.

Dedupe

500 objectstack and 300 objectui issues (open and closed, by last update) scanned for a navigation item's label, visible, resolveNavItemLabel or useNavMemberGates together with data, dataSource, binding or count: no card carries this.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 18, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: p2 · domain:spec · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-18T13:58Z

    Triage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.

    • Fresh read on origin/main: packages/spec/src/ui/app.zod.ts gives a navigation item's label as an I18nLabel, with no data root. The premise holds.
    • Grade p2, without target:v18. It is a ruled consumer (cloud#3002 A, 6248811507), so the floor is p2. It is purely additive: a new optional data source on a navigation item. Its one consumer is a sidebar label for single-environment organizations.
    • Lane: domain:spec first, by contract-first: the spec seat picks between the card's two shapes. The objectui half is a follow-up card at the spec landing. The cloud sidebar row waits on both.
    • Startup-scope axis, for the spec seat's reading: shape 1 (one data source on a nav item, as a page element has) reuses an existing declared surface. Shape 2 adds a new predicate root.

    Generated by Claude Code

  2. objectstack-fleet commented on Oct 18, 2026

    @objectstack-fleet
    ContributorAuthor

    Regrade: p2 → p1 + target:v18 · correcting this seat's 6250784407 · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-18T22:10Z

    Triage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.

    • This seat's error. 6250784407 held the grade at p2 behind a question to the maintainer: must a purely additive key land before 18.0? The maintainer had already answered it on 2026-10-14. The protocol-first rule as amended (6163539661, then 6164155198 for every lane) says every metadata-protocol change, widening included, is p1 + target:v18. The reason given: objectui waits on the spec key, and then on its release.
    • This card is on that surface: what @objectstack/spec accepts for a navigation item (packages/spec/src/ui/app.zod.ts). Fresh read on origin/main d79dc4883c: the file is unchanged since the first read.
    • Not the held class. 6164155198 held four whole-feature widenings for the maintainer's call (PDF/print, currency conversion, template install). This is one optional key with a ruled consumer (cloud#3002 A).
    • Lane unchanged: domain:spec first. The objectui half becomes a card when the spec key lands.

    Generated by Claude Code

  3. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-10-19T00:44Z · a navigation item may declare one API data source, by reference to the page element's ElementApiDataSourceSchema, and its visible reads data.* from it, as do its label and record target where the ruled sidebar row needs them (the card's shape 1; the spec half of the cloud#3002 A sidebar row)
    Session: session_01WnW7HvfnfHbLg1fpkHX3Mu (the seat; the dev is a subagent of this session, mode:subagent, told apart by the branch)
    Account: os-warren (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-24332-nav-item-api-data-source
    Worktree: objectstack-issue-24332
    Domain: domain:spec
    Seat: domain:spec#1 (seat post #6017)
    File surface (read on origin/main ee882b845f; line numbers move; stop on a breach and explain it in the report):

    Route: shape 1, by the seat (四维分析)

    The card leaves the shape to this seat. The four axes point the same way, so this is the seat's call, not a decision card. It is a call with a premise: the dispatch asks the dev to test that premise first, and to report a fork rather than build if it fails.

    • 实际业务需求:拉动是已裁的真实消费者(cloud#3002 A 的侧栏行:单环境组织显示环境名称、点击直达详情;多环境保留「环境」)。形态 1 恰好覆盖它,而且是任何「导航项随一个读数变化」的场景都能用的同一个形态;形态 2 只服务「环境数」这一个读数,写死了一个云控制面的概念。
    • 项目长远合理性:形态 1 复用平台已有、已受治理的读门(ElementApiDataSourceSchema:同源、GET、不带凭据、path 选子树),一个绑定形态、一套校验;形态 2 把控制面特有的「当前组织的环境数」做成平台谓词根,违反 app/platform 边界(只凭元数据、不知道这家公司就写不出来 ⇒ 属于应用,不属于平台),长期还会被要求再加第二个、第三个特例根。
    • 防 AI 写错:形态 1 让 AI 只学一种数据绑定拼法,页面元素与导航项同形,严格 schema 在 parse 时响亮拒绝错键;没有声明数据源却读 data.* 的导航项,应在发布时被拒,而不是静默渲染成空。形态 2 多一个只在导航里存在的根,恰是 AI 混用的温床。
    • 创业阶段不扩散需求:形态 1 是一个可选键、按引用复用既有 schema,新增面最小;形态 2 新增一个谓词根及其运行时供给。

    推荐 = 形态 1。 长远合理性领起:复用已受治理的读门,平台面只长一个通用键。

    Premise (falsifiable, tested before building): the platform already has one expression dialect that a page element uses to read data.* in a text hole and in its gate. A navigation item's visible and its label, and its recordId if the ruled row needs a data-driven target, can read data.* through that same dialect without a second spelling. If the label or the target can only take data through a new dialect, the dev builds nothing for that part and reports the fork.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 24332,
    "status": "done",
    "branch": "claude/issue-24332-nav-item-api-data-source",
    "pr": "#24427",
    "session": "session_01WnW7HvfnfHbLg1fpkHX3Mu — the seat's session (mode:subagent; this run is told apart by the branch)",
    "premise_still_valid": true,
    "summary": "Built the part of shape 1 the ruling's premise covers. The shared nav base declares dataSource: ElementApiDataSourceSchema.optional() (by reference, no import cycle). All nine base-spreading branches carry it, group included. A separator refuses it with its own prescription. visible reads the bound value as data. The describe is [PLANNED — not yet rendered]. The liveness row navigation.children.dataSource is planned + authorWarn, drilled into provider / read.url / read.method / path, and the changeset is spec minor. The premise holds for visible: objectui's nav gate (useNavMemberGates → evaluateNavigationVisibility) calls the same ExpressionEvaluator.evaluateCondition that serves a page element's visibleWhen. It fails for the record target: recordId goes through objectui's applyNavTemplate ({current_user_id} vocabulary, single brace, no dots). The label's dialect would hold (${…} holes, string or inline-map member), but translateApp's bundle rung replaces a present label by entry id, and i18n-extract seeds it as a literal. So label and target are NOT built: forks in open_questions 1–2. PR is Part of #24332, draft, assignee os-warren. Card assignee untouched. Worktree removed after the PR opened.",
    "tests": "All at head 98e3434 unless noted. NEW packages/spec/src/ui/app-nav-item-api-data-source.test.ts: 19 passed. Accept on all nine branches plus the app door (top level and a group child). Five refusals are each equal to the page element binding's own issues for the same value (code + path): unknown key unrecognized_keys at [dataSource]; off-origin read.url invalid_format at [dataSource,read,url], 3 shapes; credential key; POST invalid_value at [dataSource,read,method]; the object binding. Also separator guidance, the describe marker and the planned ledger row. ABLATION, once, via scripts/ablation-replace.mjs: key renamed to dataSourceAblated, anchor x1→x0, blob 791b2943→c37dfcd0; result 17 failed / 2 passed (the separator test and the ledger test stay green, as expected); restored, blob == HEAD, git diff HEAD empty. The tests import ./app.zod from src, so there is no dist leg. Spec full suite: 697 files / 21270 passed, at 82e723d (the later merge brought no packages/spec change). Spec typecheck exit 0 (check:test-typecheck OK). Spec check:generated after a rebuild at 98e3434: all 14 artifacts up to date. Consumers (downstream of @objectstack/spec): @objectstack/lint full suite 145 files / 6864 passed; @objectstack/metadata-protocol full suite 246 files passed, 3 skipped (20655 tests). @objectstack/cli unit tier: the FULL run hit a 540 s bound (exit 124, NOT MEASURED, declared to CI). Narrowed run of nav-contribution-groups* and test/i18n-* (non-e2e): 25 files / 298 passed. Liveness-lint probe: one liveness-planned-property finding for a top-level entry carrying the key; the lint fans out over top-level entries only.",
    "gates": "Derived list: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on the real diff, 111 families, plus local-only check:dev-prereqs. All 112 run locally on 98e3434, each exit captured before any pipe. --ran reconciliation: 111 derived, 111 run, 0 NOT-MEASURED, 0 UNRUN. Every family exit 0 except pnpm check:platform-checklist exit 1, a main-red finding (out_of_scope_findings[0]); this diff touches neither file. Six families first answered exit 3 PREREQUISITE NOT MET, then exit 0 after the consumer closure build (turbo, 62/62 cache hits): check:doc-formula-expressions, check:doc-security-posture, check:skill-examples, check:docs-transcript-drift, check:lean-entry-closure, check:dual-build-cjs-loads. The battery was cut by the container restart after command 89 and resumed from command 90 in the foreground on the same head. Slowest gates: check:type-check-debt 373 s, check:query-options-erasure 228 s, check:slot-lookup 114 s. PR #24427 check-runs, read once at open (head 98e3434, 24 runs): 'No other open PR may claim the same issue' success; 'Packed-tarball smoke (opt-in)' skipped; in_progress: Lint · CI, release & engine gates, Lint · docs & code guards, Lint · scoped · tooling self-tests, Lint · scoped · slot lookup & corpus, Lint · scoped · query-options erasure, Lint · repo & tooling gates, Lint · PM dispatch-gates, Lint · ESLint, Type Check · workspace, Type Check · consumer gates, Type Check · debt ledger, Type Check · source gates, Part-of PR must not also close its card, filter, Spec property liveness, The card this PR closes must claim this branch, Governed Surface Queue Guard, Flag docs affected by code changes, No other open PR may claim the same single-writer path, Check Changeset, Check Documentation Links, Check PR Size. Not waited on.",
    "mcp_calls": "0 — no MCP GitHub tool was called",
    "api_writes": "2 — two fleet-write relay strokes, each one POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]. (1) pr_create: POST /repos/objectstack-ai/objectstack/pulls (draft) then POST /repos//issues/24427/assignees [os-warren]; read-back: 9221 bytes sent, 9221 stored, assignee listed. (2) comment: POST /repos//issues/24332/comments (this report). Plus git push of the branch: empty-branch probe, then 4 commit pushes; git, not REST. Reads only otherwise: REST GET of the card, its comments, PR #24427 and its check-runs.",
    "line_budget": {
    ".changeset/24332-nav-item-api-data-source.md": "+13 / -0",
    "content/docs/references/ui/app.mdx": "+234 / -45 (generated by gen:docs)",
    "packages/spec/authorable-surface/ui.json": "+9 / -0 (generated by gen:schema in the build)",
    "packages/spec/liveness/app.json": "+37 / -0",
    "packages/spec/liveness/state-counts/app.md": "+1 / -1 (generated by gen:liveness-counts)",
    "packages/spec/src/ui/app-nav-item-api-data-source.test.ts": "+156 / -0",
    "packages/spec/src/ui/app.zod.ts": "+62 / -2"
    },
    "files_changed": [
    ".changeset/24332-nav-item-api-data-source.md",
    "content/docs/references/ui/app.mdx",
    "packages/spec/authorable-surface/ui.json",
    "packages/spec/liveness/app.json",
    "packages/spec/liveness/state-counts/app.md",
    "packages/spec/src/ui/app-nav-item-api-data-source.test.ts",
    "packages/spec/src/ui/app.zod.ts"
    ],
    "deviations": [
    "PR body opens Part of #24332, not a closing keyword: the label and target halves stay open on the card as forks.",
    "The ledger row carries authorWarn: true + authorHint; the dispatch named only planned. Precedent: the same file's navigation.runAction row before objectui#5216. It reaches top-level entries only. Drop it if the seat prefers the emptyState precedent (no warning).",
    "The ledger row is drilled into four children (provider, read.url, read.method, path). check:liveness refuses an undeclared container inheritance on the new container key.",
    "dataSource was added to SEPARATOR_NAV_ITEM_GUIDANCE, so the separator's base-key prescription list stays complete.",
    "@objectstack/cli unit tier narrowed: the full tier exceeded a 540 s bound (exit 124). Ran the i18n-extract and nav-contribution-groups files instead; the full tier is declared to CI.",
    "The gate battery was interrupted by the container restart after command 89 of 112 and resumed from 90 on the same head; results 1–89 are kept.",
    "origin/main moved to 8baeda5 after the merge (one commit, packages/platform-objects only, no overlap). It was not re-merged, per the after-PR merge rule.",
    "Commit trailers use AGENTS.md's model-free pair (the pre-push hook refuses a model id). The PR footer is AGENTS.md's session-URL form, not the harness reminder's form."
    ],
    "open_questions": [
    {
    "question": "Label fork. How does a data-bearing navigation label meet translation? translateApp (packages/spec/src/system/i18n-resolver.ts) replaces a present label with the bundle entry keyed by the entry id (rung 1). walkNavigation in packages/cli/src/utils/i18n-extract.ts seeds every present label into the bundle as a translatable literal. The ruled entry nav_environments has a zh-CN bundle entry, so a ${data.…} label there would render 「环境」 in zh-CN.",
    "options": [
    "A — ${…} hole in label (the page element's text-hole dialect, string or inline-map member, objectui#12936). Rung 1 skips a hole-bearing label, and i18n-extract does not seed one; its translation lives in an inline locale map. Business: covers the row only through a conditional expression that carries the noun per locale. Long term: rung 1 starts judging a label by its text shape. Anti-AI-error: two label regimes told apart only by spelling. Scope: changes two published contracts (translateApp, i18n-extract) plus objectui's label resolver. Effort M.",
    "B — ${…} hole in label, rung 1 unchanged: every locale's bundle value must repeat the hole. Business: works only once cloud deletes or rewrites its zh-CN entry. Long term: expression source in translation bundles. Anti-AI-error: worst of the three, because an existing bundle entry silently wins over the data label and nothing refuses it. Scope: smallest spec diff. Effort S spec, M objectui.",
    "C — no hole in label. One declared text key reads a dotted path into the bound value (the binding's own path grammar, refused at parse otherwise) and is shown when it selects a non-empty string. label stays the translatable fallback: 「环境」 through the bundle for many environments, the environment's name for one. Business: models the ruled row on one entry. Long term: translation rungs untouched, but it is a second way to read data (a path beside the CEL root). Anti-AI-error: a strict key with loud refusals. Scope: one optional key. Effort S spec, M objectui."
    ],
    "recommendation": "C. Led by anti-AI-error and long-term fit: it keeps rung 1's meaning, has no silent-override trap (B) and no text-shape rule (A). Its cost is a second data-read spelling, which the seat's premise ruled out for a dialect, so this is the seat's or maintainer's call."
    },
    {
    "question": "Target fork. How does an entry's record target read data? recordId, and the filters / params values, go through objectui's applyNavTemplate (objectui@749ed76 packages/layout/src/NavigationRenderer.tsx). Its vocabulary is {current_user_id}, {current_org_id} and a context selector's {id}: single brace, no dots. A ${data.id} there is taken as written.",
    "options": [
    "A — ${…} holes in recordId beside {…}. Two template dialects in one field, and the user id gets two spellings ({current_user_id} and ${current_user.id}). Effort S spec, M objectui.",
    "B — grow the nav template to {data.PATH}. One dialect per field, but a second spelling of a data read (data.x in visible, {data.x} here). Effort S spec, M objectui.",
    "C — a declared path key for the record id (the binding's path grammar), used only when it selects a non-empty string. Otherwise the entry falls through to its list landing, as resolveHref already does for an unresolvable recordId, so one entry serves both cases of the ruled row. Pairs with label option C. Effort S spec, M objectui.",
    "D — no data target: two entries with complementary visible gates, the single-environment one opening an app page that finds its own record. No platform key; an app-specific page instead. Effort S in cloud."
    ],
    "recommendation": "C, for the same reasons as label option C, with one path grammar for both keys. D if the maintainer prefers no new spec key (the startup-scope axis), at the cost of an app-specific page and a still-static label."
    },
    {
    "question": "Narrowing arm, NOT shipped: refuse at parse a visible that reads data.* on an entry with no dataSource. It is BREAKING and needs an ADR-0087 disposition. Measured reach in this repo, origin/main 71fa166: 0 data. reads among the nav visible predicates in the 38 navigation-declaring files. Control: the same files carry nav predicates on current_user, e.g. packages/spec/src/conversions/registry.ts:2091. objectui main 749ed76: its 54 navigation-declaring source files carry no authored string nav predicate at all, control 0 too, so that corpus measures nothing. cloud: not reachable from this container.",
    "options": [
    "A — its own PR after this one, BREAKING with a tightening disposition. Loud at publish instead of a gate with nothing to read. Needs a CEL root reader in spec (whether one exists is not measured). Effort S–M.",
    "B — ship it with the objectui renderer half, when data first means something on a nav entry.",
    "C — never: rely on the renderer withholding a faulting nav gate. Whether an unbound data read faults is NOT MEASURED."
    ],
    "recommendation": "A, led by anti-AI-error (declared is enforced). The measured in-repo reach is zero, so the break costs nobody measured here. C trades a parse-time refusal for renderer tolerance."
    },
    {
    "question": "Refusal wording. The reused binding's refusal sentences name a page element on the navigation door too. The read.url refusal reads "read.url of a page element's api data source must be …", and the strict surface says "this element api data source". page.zod.ts is a hold-off for this card.",
    "options": [
    "A — neutral wording in page.zod.ts ("an api data source") for both doors: one text, still one schema. Effort S.",
    "B — a surface-parameterised factory in page.zod.ts, one instance per door. One accept set, but the schema is no longer one referenced instance. Effort S–M.",
    "C — leave it: a nav author reads 'page element' and still gets the right fix."
    ],
    "recommendation": "A, sequenced with #24300 (same file, same describe). It is the smallest change, and the refusal text is release-text that should be true at both doors."
    }
    ],
    "out_of_scope_findings": [
    "class: a · reach: pnpm check:platform-checklist exits 1 on a tree whose diff touches neither file (PR head 98e3434 = origin/main 71fa166 + this card). The finding: areas/access-security.json: ABSENT SYMBOL packages/core/src/security/resolve-authz-context.ts#legacyGrantAnchorRetired. Evidence, two legs on origin/main 71fa166: git grep -c legacyGrant in that file answers 0 (exit 1); control git grep -c export answers 13 (exit 0). The anchor sits at docs/qa/platform-checklist/areas/access-security.json:2823. The file's newest main change is b2e7870 (#24373). Likely red in CI's required Lint & Repo Gates on every PR until repointed. Dedupe words: legacyGrantAnchorRetired, platform-checklist ABSENT SYMBOL, access-security.json, resolve-authz-context",
    "carrier: the objectui renderer half the seat files at this landing · read points at objectui@749ed76 (unchanged from pin 0707b4ae37): (1) packages/app-shell/src/hooks/useNavMemberGates.ts#useNavMemberGates, one shared evaluator from useExpressionContext handed to evaluateNavigationVisibility, which needs a per-entry scope carrying data from the entry's binding (packages/react/src/element-data-source/useElementApiDataSource.ts is the existing read hook); (2) packages/layout/src/NavigationRenderer.tsx#passesNavItemGuards / #hasVisibleNavigationItems, evaluateVisibility(item.visible) with no entry scope; (3) #resolveNavItemLabel → presentNavItemLabel renders a string verbatim, which is the read point only if the label fork is ruled; (4) #resolveHref / applyNavTemplate (TEMPLATE_VAR_RE), only if the target fork is ruled; (5) packages/types/src/zod/app.zod.ts NavigationItemObject is a non-strict z.object and strips dataSource, so objectui validate drops it today · noted, not filed",
    "carrier: #24300 · the spec text is stale against the objectui pin. ElementApiDataSourceSchema's [PLANNED — not yet rendered] describe and PageComponentSchema.visibleWhen's 'mounts NO data root' text contradict objectui pin 0707b4ae37, where packages/react/src/SchemaRenderer.tsx:1157/:1231 binds the API answer as data. Same reading at main 749ed76 :1174/:1248 · noted, not filed"
    ]
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    待裁:导航项的标题与跳转目标怎么读数据 · domain:spec seat 1 · os-warren · session session_01WnW7HvfnfHbLg1fpkHX3Mu · 2026-10-19T03:35Z

    ACCEPT → PR #24427 comment 6259075080 (contract review PASS 6259056316); ready and auto-merge armed.

    ⛔ Not a claim. The card stays pm:dispatched while PR #24427 (the visible half) is in review. When it merges, this card flips pm:dispatched → needs-user-decision in one replace, with a Release: line. The landing pointer on this card names that flip.

    一句话问题:单环境组织的侧栏要「显示环境名称、点击直达详情」(cloud#3002 A),可导航项的标题和跳转目标今天只能写死。PR #24427 已经让导航项能读一个 API 数据源、并按数据决定显不显示;标题和跳转要不要也读数据,以什么形状读,是公开契约的形状分歧,归维护者。

    背景:本席派发时裁了形态 1,并挂了前提:标题与跳转目标能用 visible 那同一套表达式拼法读 data.*。dev 实测证伪了这个前提(报告 os-dev-report 见本卡上方),本席复核三处读数如下(origin/main 538f923cb9,objectui 749ed76):

    • 标题:translateApp 第 1 档按导航项 id 用翻译包里的 apps.APP.navigation.ID.label 整体替换已写的标题(packages/spec/src/system/i18n-resolver.ts:1380、:1388);i18n-extract 把每个已写的标题当字面量收进翻译包(packages/cli/src/utils/i18n-extract.ts:2051)。cloud 的 nav_environments 有 zh-CN 翻译条目(卡片正文,本会话读不到 cloud 仓)。所以一个带 ${data.…} 的标题在中文界面会被翻译条目盖掉,静默显示「环境」。
    • 跳转:objectui 的 applyNavTemplate 只认 {current_user_id}、{current_org_id} 这类单括号、不带点的变量(objectui:packages/layout/src/NavigationRenderer.tsx:763)。${data.id} 会被原样当作记录 id。
    • 已落地(待合并)的一半:dataSource 键 + visible 读 data,台账 planned(PR feat(spec): a navigation entry may declare one API data source, which its visible reads as data #24427)。

    Governing text: docs/NORTH-STAR.md 优先级第 4 条:声明了的在运行时兑现,错的必须被响亮拒绝。.claude/skills/pm-dispatch/references/decision-analysis.md〈应用仓推荐序特例〉:「平台相关的功能,应该尽量先改平台。」(维护者 2026-10-18,裁决台账 6245607810)。AGENTS.md Prime Directive #12 (contract-first)。
    是否改协议: 是。A、B、C 都给已发布的导航项 schema 加东西(放宽,Clause-②: yes),D 不改协议。

    问题 1:标题与跳转目标

    选项 做什么 客户可感知后果 工作量
    A 文本洞 label 允许 ${…} 洞;翻译第 1 档遇到带洞的标题就跳过;recordId 再长一套 ${…} 能做出这一行;同一字段里两套模板拼法({current_user_id} 与 ${…}),翻译规则开始按字面形状判断 spec M(改 translateApp 与 i18n-extract 两个已发布契约),objectui M
    B 文本洞、翻译不动 label 允许 ${…},翻译条目照旧整体替换 中文界面静默显示「环境」,没有任何拒绝;每个语言的翻译值都得重复写洞 spec S,objectui M;无先例
    C 声明式路径键 新增两个可选键(暂名 labelPath / recordIdPath),值复用数据源 path 的点路径语法,在 parse 时严格校验;选中非空字符串才用,否则回落到可翻译的静态 label 与列表落地页 一个导航项同时覆盖单环境(显示环境名、直达详情)和多环境(显示「环境」、进列表);翻译规则不动 spec S(先例 PR #24427,+512/−48,7 个文件),objectui M
    D 不加平台键 两个导航项,用互补的 visible 切换;单环境那项打开 cloud 自己写的一个页面,由页面去找记录 标题仍是写死的(做不到「显示环境名称」);cloud 自建一个页面绕过平台 cloud S;spec 零

    业务含义直译:A 像把公式写进菜单名、再改翻译系统去认公式;B 像菜单名里写了公式但翻译直接覆盖它;C 像菜单项上多一个「取值位置」字段,取不到就显示默认名;D 像让应用自己做一个跳转页。

    四轴(业务立场):

    • 项目长远合理性(领起):两年后平台的样子应是「导航项可以绑定一个只读数据源,并在声明的位置取值,取不到就回落到默认」。这与页面元素已有的 API 绑定同形,翻译规则不需要知道表达式长什么样。C 最贴近;A 让翻译层按字面形状分两套规则;B 把表达式塞进翻译包;D 让应用仓自建平台能力,违背「平台相关的功能,应该尽量先改平台」。
    • 实际业务需求:已裁的真实消费者是 cloud#3002 A 的侧栏行,需要标题和跳转都读数据。A、C 能做全,B 在中文下做不对,D 做不到「显示环境名称」。
    • 防 AI 写错:C 是严格键,写错路径在 parse 时响亮拒绝,取不到时有确定的回落;B 是最坏的:AI 写的带洞标题被已有翻译静默盖掉,没人看得到错。A 要求 AI 记住「带洞的标题不进翻译包」这种隐形规则。
    • 创业阶段不扩散:D 平台零新增;C 两个可选键;A 改两个已发布契约。

    推荐 = C。 回退 = D(若维护者不想为这一行加平台键)。只看①选 C;②③④ 是否翻转:否(④更偏 D,但 D 违背平台优先的口径,且满足不了已裁需求)。置信缺口:cloud 仓本会话读不到,nav_environments 的现有翻译条目与环境详情页的路由按卡片正文采信,未实测。

    问题 2:没有声明数据源的导航项读 data.*,要不要在 parse 时拒绝(dev 报告的收窄臂,PR #24427 没带)

    选项 做什么 后果 工作量
    A 现在单独一个 PR parse 时拒绝无 dataSource 的导航项在 visible 里读 data.*,BREAKING,带 ADR-0087 处置 本仓实测 0 处会被拒(38 个声明导航的文件,对照组 current_user 有命中),在 18.0 窗口里收紧最便宜 spec S–M(需一个 CEL 根读取器,是否已有未实测)
    B 随 objectui 渲染半边一起 data 在导航上第一次有意义时再收紧 中间这段时间写错的 data.* 静默无效 同 A,往后挪
    C 不做 依赖渲染器对出错门控的处理 契约复核 6259056316 在 pin 上读到:读不到的 data 根让 CEL 求值报错,导航门控按「出错即隐藏」处理,所以这样的条目被静默藏起来,不会误显示;但作者看不到任何报错 零

    推荐 = A。 防 AI 写错领起(声明即强制):C 的读数说明后果是「条目静默消失」而不是越权显示,风险不高,但作者在写的那一刻仍得不到提示;实测零影响面,18.0 窗口内收紧代价最低。回退 = B。

    裁后执行:问题 1 选 C ⇒ 本卡第二个 PR 由本席派发:两个路径键加 schema 与 pin,台账 planned,objectui 渲染半边另立 objectui 卡(本席落地 PR #24427 时一并立)。选 D ⇒ 本卡在 PR #24427 合并后按已交付关闭,cloud 侧另由路由 cloud#3002 的席位承接。问题 2 选 A ⇒ 本席另立一张 domain:spec 收窄卡,p1 + target:v18,Clause-②: no (narrowing)。

    os-decision-facets

    • ① 项目长远合理性:C 复用数据源已有的点路径语法,不改翻译规则,特例最少;A 让翻译层多一套按字面判断的规则,契约增生最多。
    • ② 实际业务拉动:今天撞上的是 cloud 单环境组织的侧栏这一行(cloud#3002 A 已裁);仓内无其它导航项需要读数据。
    • ③ 防 AI 犯错:C 是闭合的严格键、写错即拒;B 让 AI 的错误被翻译静默掩盖。
    • ④ 创业阶段不扩散:D 平台零新增;C 两个可选键,A 改两个已发布契约。

    Prior rulings read: spec,objectui,navigation,item,read,data,label,visible,gate,follow,live,count (+4 more) → 331 hits; none answers whether a navigation label or target reads data (ADR-0002 §1, ADR-0068 D1, ADR-0076 D12 by term match only); thread: 0

    Decided by the seat, not boxed: the reused binding's refusal sentences name a page element on the navigation door too (dev open_questions[3]). That is a wording fix with no contract change, so it rides #24300, the same file and the same describe: ElementApiDataSourceSchema's refusal and strict-surface text say "an api data source" for both doors. Rider note posted on #24300.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    席位意见(增量)· director seat (summon #43 continuation 5, session_01QJC7i981TSs9L1CGJK59Ax), read as batch #369 item 3, 2026-10-19T03:50Z

    On the domain:spec seat's 待裁 (6259088926: question 1, how a navigation item's label and target read data — C recommended; question 2, whether data.* without a declared dataSource is refused at parse — A recommended), relayed by the maintainer in this session's chat: 「等你裁定(决策分析已落在 #24332,6259088926)」. The card is not labelled needs-user-decision (the seat flips it after PR #24427 merges); the maintainer asked for it now, so it is read as a decision card. Under the maintainer's rule of this act (#24444) this delta recommends one option per question.

    Premises re-run in this act — objectstack origin/main 9112bcd8, objectui origin/main 09eadf4e:

    1. A translation replaces a navigation item's label whole, keyed by the item's id (packages/spec/src/system/i18n-resolver.ts:1380, :1388), and i18n-extract collects every authored label as a literal (packages/cli/src/utils/i18n-extract.ts:2051). So a label carrying a ${data.…} hole is covered by its zh-CN entry and the Chinese sidebar shows 「环境」 silently. Holds.
    2. objectui's navigation template knows single-brace, dot-less variables only (packages/layout/src/NavigationRenderer.tsx:763, applyNavTemplate :765): ${data.id} in recordId is passed through as a literal record id. Holds.
    3. PR feat(spec): a navigation entry may declare one API data source, which its visible reads as data #24427 (the visible half: a navigation item declares one API data source and gates on data.*) is open, not a draft, +512/−48 over 7 files, not merged; the API shows no auto-merge armed, against the seat's 「auto-merge armed」 — the seat re-checks before relying on it. Holds otherwise.
    4. The ruled consumer is one row: cloud#3002 A's sidebar item (6248811507). The maintainer's 口径 「平台相关的功能,应该尽量先改平台」 applies.

    Facets, question 1: ① C — in two years a navigation item binds one read-only data source and takes its label and target from declared paths, falling back to the translatable static label and the list page when the path yields nothing; that is the page element's binding reused, and the translation layer never learns an expression's shape; A teaches the translation layer to read a literal's shape (two template syntaxes in one field); B lets a translation entry cover the expression silently; D builds the capability in the application repository, against the 口径 · ② one real consumer today, and A and C are the two that can do all of it; B is wrong in Chinese; D cannot show the environment's name · ③ C is a strict key: a wrong path is refused at parse and a missing value falls back deterministically; B is the worst (an AI's hole is covered by an existing translation and nobody sees it); A needs an invisible rule (「a label with a hole stays out of the bundle」) · ④ C adds two optional keys with PR #24427 as the precedent (+512/−48); A changes two published contracts; D adds nothing to the platform but puts a platform capability in cloud.
    Facets, question 2: ③ leads — declared is enforced: today an undeclared data.* in visible makes the CEL evaluation fail and the gate hides the item (the contract review 6259056316 read this at the pin), so the risk is a silently vanished item, not a wrongly shown one; the author still learns nothing at authoring time; the repository measures 0 affected declarations (38 files declaring navigation), and the 18.0 window is where a narrowing is cheapest (ADR-0087 conversion) · ① one rule, 「a data root needs a declared source」, at the parse door · ④ spec S–M, its own PR.

    Recommend question 1 C · question 2 A. 口径 with the letters: (1) two optional keys on the navigation item (the seat names them; labelPath / recordIdPath provisionally), their values the data source's dot-path syntax, validated strictly at parse, used when they select a non-empty string, else the translatable static label and the list landing page; one item covers the single-environment organisation (the environment's name, straight to its detail) and the multi-environment one (「环境」, the list); the ledger planned; the objectui rendering half on its own objectui card; (2) a domain:spec narrowing card, p1 target:v18, Clause-②: no (narrowing): a navigation item without dataSource that reads data.* is refused at parse, with the ADR-0087 conversion; (3) cloud#3002's sidebar row waits on the next pin move. Not taken: question 1 A (two template syntaxes in one field; the translation layer reads shapes), B (silent in Chinese), D (a platform capability built in cloud, against the 口径, and it cannot show the name); question 2 B (a silent window), C (no authoring-time refusal).

    os-decision-facets

    • ① 项目长远合理性:问题 1 选 C —— 导航项绑一个只读数据源、按声明路径取值、取不到就回落,与页面元素同形,翻译层不认识表达式;问题 2 选 A —— 「data 根须有声明的来源」一条规则在 parse 门。
    • ② 实际业务拉动:真实消费者一行(cloud#3002 A 的侧栏);A、C 能做全,B 中文下做错,D 显示不了环境名。
    • ③ 防 AI 犯错:C 是严格键,路径写错即拒、取不到确定回落;B 最差,AI 写的洞被已有翻译静默盖掉;问题 2 今天未声明的 data.* 让条目静默消失,作者写时得不到提示。
    • ④ 创业阶段不扩散:C 两个可选键,先例 PR feat(spec): a navigation entry may declare one API data source, which its visible reads as data #24427;A 改两个已发布契约;D 平台零新增但把平台能力建在 cloud。

    自检:只看①选 C · A;②③④ 是否翻转:否。

  7. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling: batch #369 item 3 · letters Q1 E · Q2 A · maintainer 「同意」 after 「24332 是核心需求吗?如果开发复杂的话,3002 也可以删除左侧的导航项」 2026-10-19T04:04Z

    Director seat, summon #43 continuation 5, session_01QJC7i981TSs9L1CGJK59Ax, on the domain:spec seat's 待裁 (6259088926: question 1 C recommended, question 2 A recommended; the director's delta 6259247525 agreed, then re-read cloud's home page on the maintainer's question) (opening marker #12708, comment 6247058702). The maintainer's words in this session's chat: 「24332 是核心需求吗?如果开发复杂的话,3002 也可以删除左侧的导航项。其他同意」, then, on the director's revised recommendation Q1 E · Q2 A, 「同意」.

    What the ruling binds:

    1. Question 1 — E, no platform key. A navigation item's label and recordId stay static: neither ${…} holes (A, B) nor path keys (C) are added to the spec, and cloud builds no page of its own (D). objectstack-ai/cloud#3002's sidebar row (inventory 6247161029 row 1) changes from 「显示环境名称、直达详情」 to: an organisation with one environment does not show the 「环境」 item; an organisation with more keeps it. That is PR feat(spec): a navigation entry may declare one API data source, which its visible reads as data #24427's visible on the item's declared data source, written in cloud after the pin move that carries feat(spec): a navigation entry may declare one API data source, which its visible reads as data #24427. The environment stays reachable from Home: the hero names it and opens it (「打开」), and 「管理」 beside it (managers) goes to the environments list. The seat's second PR (labelPath / recordIdPath) is not dispatched; no objectui rendering card is cut.
    2. Question 2 — A. A navigation item without a dataSource whose visible reads data.* is refused at parse, BREAKING with its ADR-0087 conversion, as its own domain:spec card, p1 target:v18, Clause-②: no (narrowing); in-repo impact measured at 0 declarations.
    3. This card closes delivered when PR feat(spec): a navigation entry may declare one API data source, which its visible reads as data #24427 merges (the seat's landing pointer names the flip); cloud#3002's Blocked-by on this card lifts with that merge and the pin move.
      Not taken: question 1 A (two template syntaxes in one field; the translation layer reads shapes), B (silent in Chinese), C (two keys for one row the maintainer does not need), D (a cloud-built page); question 2 B (a silent window), C (no authoring-time refusal).

    Workload of the letters: question 1: cloud S (one visible expression) after the pin move; question 2: spec S–M, one PR.

    Execution: the spec seat (session_01WnW7HvfnfHbLg1fpkHX3Mu) lands PR #24427, cuts the question-2 card, and closes this card; pointer on objectstack-ai/cloud#3002 for the repo:cloud#2 seat.

  8. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling received · domain:spec seat 1 · os-warren · session session_01WnW7HvfnfHbLg1fpkHX3Mu · 2026-10-19T04:11Z · Ruling-ref 6259393328 (batch #369 item 3: Q1 E · Q2 A).

    • Freshness: read after the director's delta 6259247525 and the ruling 6259393328, both newer than this card's last body edit. Nothing on the card contradicts them.
    • Q1 E: no second spec PR is dispatched. label and recordId stay static. This card closes completed when PR feat(spec): a navigation entry may declare one API data source, which its visible reads as data #24427 merges, and the landing comment names that flip. Until then it stays pm:dispatched; PR feat(spec): a navigation entry may declare one API data source, which its visible reads as data #24427 is in the merge queue (added_to_merge_queue 2026-10-19T03:39Z).
    • Q2 A: filed as spec(ui): a navigation entry with no dataSource whose visible reads data.* is refused at parse (BREAKING, ADR-0087), question 2 of #24332's ruling #24450 (p1, target:v18, pm:queue, domain:spec). It is not dispatched by this seat, which stands down after its cards in hand land, by the maintainer's word 「当前任务处理完,合并后就下班」.
    • cloud#3002: the sidebar row's pointer belongs on objectstack-ai/cloud#3002 for the repo:cloud#2 seat, as the ruling names. This session cannot reach the cloud repository, so that pointer is the director seat's or the cloud seat's to post.
    • One reading the ruling's execution needs, measured at objectui origin/main 4016370 and the pin 0707b4ae37 (git grep -c dataSource over types/src/zod/app.zod.ts, layout/src/NavigationRenderer.tsx, app-shell/src/hooks/useNavMemberGates.ts: 0 in each; control visible: 8 and 21): the cloud row's new form ("one environment: the item is not shown") needs the console to read a navigation entry's dataSource and give its value to visible as data. At the pin it does neither. NavigationItemObject (objectui:packages/types/src/zod/app.zod.ts) is a non-strict object that strips dataSource. passesNavItemGuards evaluates item.visible with no per-entry scope (objectui:packages/layout/src/NavigationRenderer.tsx). So a visible that reads data faults, and the entry is withheld for every organisation (contract review 6259056316 ③). The ruling says "no objectui rendering card is cut". This seat reads that as the label and target half (option C's), and the visible half as still owing its objectui card, the carrier of the planned ledger row (liveness/app.json navigation.children.dataSource). That reading is put to the maintainer in this session's chat. Until it is answered, no objectui card is filed.

    Generated by Claude Code

  9. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Maintainer's answer · domain:spec seat 1 · os-warren · session session_01WnW7HvfnfHbLg1fpkHX3Mu · 2026-10-19T04:25Z · on 6259468396's open reading.

    The question, put in this session's chat: should objectui get a card for the navigation visible reading an entry's dataSource? The maintainer answered 「不立」.


    Generated by Claude Code

  10. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    PR #24427 was removed from the merge queue on a new timeout signature (reference-sites.derivation.test.ts, Test Core 6/6). Full signature and first reading: PR comment 6259663860 · 2026-10-19T04:28Z. The dev measures the derivation time at the head vs origin/main before any re-queue.


    Generated by Claude Code

  11. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 24332,
    "status": "done",
    "branch": "claude/issue-24332-nav-item-api-data-source",
    "pr": "#24427",
    "session": "session_01WnW7HvfnfHbLg1fpkHX3Mu — the seat's session (mode:subagent; patch round on the same claim, told apart by the branch)",
    "premise_still_valid": true,
    "summary": "Patch round for queue ejection run 39195742017, job Test Core (6/6). The failing test was @objectstack/metadata-protocol src/reference-sites.derivation.test.ts, 'two derivations of the same schemas agree exactly', timed out at 5000 ms. VERDICT (b): the PR does not make the derivation slower. Durations, 3 runs each on this container: PR head 98e3434 827 / 784 / 810 ms (mean 807); merge base 71fa166 (origin/main as the PR was built on, i.e. the PR minus its own diff) 813 / 752 / 853 ms (mean 806). Ratio 1.00×. Both sides sit near 16% of the 5000 ms budget, so the CI timeout needed about 6× this box's time on either side. NO PUSH; branch head unchanged at 98e3434. One structural datum, not a timing one: the new key makes AppSchema's input JSON Schema larger (41055 → 61476 bytes, the binding inlined per branch), but z.toJSONSchema(AppSchema) takes 15 ms → 16 ms, inside the noise of an ~800 ms test. The seat keeps the signature on the PR and the card and re-queues once.",
    "tests": "Worktree recreated on the existing branch at 98e3434 (no local or remote change), pnpm install --frozen-lockfile exit 0. Closure built through the verify lock with pnpm turbo run build --filter='@objectstack/metadata-protocol^...' --concurrency=2: 12/12 tasks, all cache hits. Spec dist carries this PR's describe and build-input hash 66b6f1d944ef. Control: a second worktree, detached at 71fa166, built the same closure through the lock (12 tasks, 1 cached, 2m29s); its spec dist has 0 hits for the new describe. Measurement command, per run, through the lock, no bare --: pnpm --filter @objectstack/metadata-protocol exec vitest run --maxWorkers=2 --reporter=json --outputFile=FILE src/reference-sites.derivation.test.ts. Every run exit 0, 25 tests per file. 'two derivations of the same schemas agree exactly': PR run1 827 ms, run2 784 ms, run3 810 ms; base run1 813 ms, run2 752 ms, run3 853 ms. Probe in each tree's metadata-protocol package: z.toJSONSchema(AppSchema, { unrepresentable: 'any', io: 'input' }) is 61476 bytes in 16 ms at the PR head and 41055 bytes in 15 ms at the base. No ablation: no behaviour changed in this round.",
    "gates": "No code changed in this round, so no gate was re-run locally; the derived set and its results stand as reported at 98e3434. Queue reading from the seat: run 39195742017, job Test Core (6/6) 117878093645 failed on the timeout above; removed_from_merge_queue at 04:09:49Z. The PR's check-runs were not re-read here.",
    "mcp_calls": "0 — no MCP GitHub tool was called",
    "api_writes": "1 — one fleet-write relay stroke (POST /repos/objectstack-ai/objectstack/dispatches as objectstack-fleet[bot]) executing POST /repos//issues/24332/comments (this report). No git push this round.",
    "line_budget": {
    "(this round)": "+0 / -0 — no file changed; PR diff unchanged from 98e3434"
    },
    "files_changed": [],
    "deviations": [
    "The control side is the PR's merge base 71fa166, not the current origin/main fa26308. The merge base is the tree the PR head was built from, so the two sides differ by exactly this PR's diff; a newer main would mix in other commits.",
    "Closures were built with pnpm turbo run build --filter=...^... --concurrency=2, not the plain pnpm --filter ... build. Same task set; turbo's input-hashed cache is what made the PR side 12/12 hits, and the restored spec dist carries this PR's text and hash.",
    "Both worktrees were removed after measurement (node_modules first, no --force)."
    ],
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: the seat · Test Core (6/6) timeout on reference-sites.derivation.test.ts 'two derivations of the same schemas agree exactly' is a NEW signature. It measures ~0.8 s locally on both sides, so the 5000 ms default timeout was missed by a CI shard running about 6× slower. AGENTS.md's 10 s wall rule does not trigger at 0.8 s · noted, not filed. Dedupe words: reference-sites.derivation, two derivations of the same schemas, Test timed out in 5000ms, Test Core (6/6)"
    ]
    }


    Generated by Claude Code

  12. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Release-scope note · epic PM session_01Y5583bs2RDd53MH6kM1ca3 (os-steve) · 2026-10-19T04:39Z. target:v18 is removed from this card.

    The maintainer wrote in the epic seat's chat: 「把你建议的那批卡移出 v18」. 18.0 ships for fresh installs, on the single posture only, as soon as possible (ruling records 6259608309 and 6259698357 on #15194).

    This changes only the release target. Your work in flight is not stopped. It lands whenever it is ready and ships in the next release after it merges.

  13. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed and closed · domain:spec seat 1 · os-warren · session session_01WnW7HvfnfHbLg1fpkHX3Mu · 2026-10-19T05:03Z · PR #24427 · Ruling-ref 6259393328.

    reading result
    origin/main 26501ae24f "feat(spec): a navigation entry may declare one API data source, which its visible reads as data (#24427)"
    merge queue (refs/heads/gh-readonly-queue/*) 0 entries for PR #24427
    PR object merged at 2026-10-19T05:02Z (second queue entry, after the load-timeout ejection 6259663860 and the measured re-queue 6259741775)

    Delivered and closed completed, as the ruling names: a navigation entry may declare one api data source, and its visible reads it as data. The key is planned in liveness/app.json. pm:dispatched comes off in the same act.

    What stays with others:


    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions