Skip to content

[Decision] admins author Markdown docs in the console and put them on the app menu — runtime doc/book authoring surface and a doc navigation item (maintainer's stated need) #19482

Description

@os-project-manager

Path: 不写代码在运行中改应用 | 缺项——platform-core.docs-portal-render covers rendering only; no item asserts runtime doc authoring or a doc on the app menu | P2

Filed by the director seat, summon #25 (session_012GcsUbuqFGBibkEDMRC1eE), from the maintainer's words in chat (2026-09-21), verbatim: 「doc 包文档 我觉得是有需求的,可能管理员需要在界面上写一些markdown的文档。甚至加到菜单。」 Filed into the decision box as a feature / contract-shape proposal (a design card by the charter's own test); ⛔ not a claim; domain:spec because the contract half lands in packages/spec, with an objectui sub-issue to follow once ruled (rule 2: cross-repo feature = parent + per-repo sub-issues, spec first).

维护者速读

事情:您说管理员可能要在界面上写 Markdown 文档,甚至加到菜单。本席量了今天有什么:

  • 能看:控制台有文档门户(/docs/:book/:name,Markdown 渲染 + 书的侧栏,ADR-0046);文档和书都是元数据,包里带的 src/docs/*.md 构建时收集进来。
  • 能存:doc / book 都声明 allowRuntimeCreate: true,通用元数据保存门 PUT /meta/doc/:name 今天就能存一条运行时写的文档(这正是刚才更正裁决保住的那扇门)。
  • 不能写:Studio / 元数据管理里没有文档编辑器——没有 doc、book 的表单,管理员在界面上写不了。
  • 不能上菜单:应用导航项的种类里没有「文档」这一种(有 object / group / page / component / action 等);想把一篇文档放进应用菜单,今天只能拿一个 page 项硬指向文档路由,是绕法不是声明。

选项:

  • A 两半都做,spec 先行:① packages/spec 给应用导航加一种 doc 导航项(指向一篇文档名,权限随文档的 audience 走);② objectui 加「文档」编辑器(Markdown 编辑 + 预览,创建/编辑 doc,把它放进某本 book),落在 Studio 的元数据管理里。先做 ①(一张 spec 卡),再做 ②(objectui 子卡)。
  • B 只做编辑器(②),菜单继续用 page 项指向 /docs/... 路由——不改 spec。
  • C 只做导航项(①),编辑仍走 API / AI 作者,管理员暂不在界面上写。
  • D 不做,维持现状(看得到、API 能存、界面写不了、菜单上不去)。

荐 A。请回一个字母:A / B / C / D。

os-decision-facets

  • ① 项目长远合理性:文档已经是元数据(ADR-0046)、AI 已经是主要作者(ADR-0033);给它一个导航项种类和一个界面编辑器,是把已有类型接完整,不是新概念。B 用 page 硬指路由是把「文档在菜单上」写成一个绕法,将来每个消费方各自猜;A 的 ① 让菜单声明「这是一篇文档」,权限与 audience 门自然跟上。⇒ A。
  • ② 实际业务拉动:维护者当面点名的需求;今天缺的是编辑器与菜单项两件,看与存都在。⇒ 拉动真实,但两半都可以分期。
  • ③ 防 AI 犯错:doc 导航项是闭合种类,写错的文档名在保存时响亮拒绝;page 绕法里文档名藏在路由字符串里,写错静默 404。⇒ A > B。
  • ④ 创业阶段不扩散:A 新增一个导航项种类(永久义务,小)和一个编辑器;D 零义务。⛔ 不因此翻字母:需求是维护者点名的产品路(「不写代码在运行中改应用」),不是工具卡。

Prior rulings read: doc,book,navigation,menu,markdown,docs-portal → ADR-0046 D§1/§6 (docs as metadata; nav spine runtime-editable), ADR-0033 (AI as primary author), ADR-0088 §3; thread: none

推荐:A。自检:「只看①选 A;②③④ 是否翻转:否——② 支持分期(① 先),④ 反向但它管的是新键不是能力」。回退 B。置信缺口:⛔ 未量 objectui 侧 book 的运行时覆盖(ADR-0046 §6.4)是否已实现;⛔ 未量 PUT /meta/doc 对 content 的大小/清洗限制;⛔ 未量文档 audience 门(platform-core.docs-audience-gate)如何应用到导航项。

Execution, if A

  1. Spec card (this repo, domain:spec): DocNavItemSchema in packages/spec/src/ui/app.zod.ts (type: 'doc', doc: <doc name>, optional book), strict like its siblings, .describe() naming the audience rule; Clause-②: yes (a new nav variant is a public-surface widening) — at-tier review; minor changeset; the console's nav renderer resolves it to /docs/:book/:name.
  2. objectui sub-issue (domain:ui, Blocked-by: the spec card until the pin covers it): a Markdown editor for doc items in Studio's metadata admin (create / edit / preview, assign to a book), reusing @object-ui/plugin-markdown; the nav renderer for the new variant.
  3. Checklist item to author (platform-checklist platform-core or studio-authoring): an admin creates a doc in the UI, puts it on the app menu, and a member without the doc's audience does not see the entry.

Generated by Claude Code

Activity

  1. os-project-manager commented on Sep 21, 2026

    @os-project-manager
    CollaboratorAuthor

    Ruling: batch #206 item 1 · letter A · maintainer 「同意」 2026-09-21T02:28Z

    Director seat, summon #25 (session_012GcsUbuqFGBibkEDMRC1eE). Presented with this seat's recommendation A from facet ①; the maintainer approved. Governing text: the maintainer's stated need (chat, 2026-09-21: 「doc 包文档 我觉得是有需求的,可能管理员需要在界面上写一些markdown的文档。甚至加到菜单。」); ADR-0046 (docs and books as metadata, nav spine runtime-editable), ADR-0033 (AI as primary author), ADR-0088 §3.

    Ruled: both halves, spec first. ① This repo: a doc navigation item variant on the app schema (packages/spec/src/ui/app.zod.ts — type: 'doc', doc: <doc name>, optional book; strict like its siblings; the entry inherits the doc's audience gate; .describe() names that rule). ② objectui: a Markdown editor for doc items in Studio's metadata admin (create / edit / preview, assign to a book) and the nav renderer for the new variant, resolving to the docs portal route. B (menu by a page item pointing at a route) and C (nav item only) are ⛔ not taken; D is refused by the maintainer's stated need.

    Execution: this card is the spec half — needs-user-decision → pm:queue in this stroke; domain:spec seat dispatches, priority:p2. Dispatch names: DocNavItemSchema beside the existing variants with the per-variant strict surface, the console-facing .describe(), Clause-②: yes (a new public nav variant widens the surface — at-tier review), a @objectstack/spec minor changeset, the generated artefacts, and a test that a doc nav item naming a doc that does not exist in the package is refused with a remedy. The dev's first readings are this card's three confidence gaps (the book runtime overlay's state in objectui, the /meta/doc content limits, how the docs audience gate applies to a nav entry). The objectui half is filed by this seat in the same stroke as an objectui card with Blocked-by: objectstack-ai/objectstack#19482 (rule 2: cross-repo feature, spec first; it unblocks when the spec ships in a version objectui can pin). A platform-checklist item (admin creates a doc in the UI, puts it on the app menu, a member outside the doc's audience does not see the entry) is authored with the objectui half, ⛔ not before.


    Generated by Claude Code

  2. os-project-manager commented on Sep 21, 2026

    @os-project-manager
    CollaboratorAuthor

    Ruling addendum: batch #206 item 1 · letter A, shape amended · maintainer 「「是」,把这条追加」 2026-09-21T02:32Z

    Director seat, summon #25 (session_012GcsUbuqFGBibkEDMRC1eE). The maintainer asked in chat 「导航菜单除了能直接引用doc,是否建议直接链接到 book?」; this seat recommended yes and the maintainer adopted it. The shape the spec half implements is therefore:

    One doc navigation item variant, targeting a book and/or a doc — at least one required.

    • { type: 'doc', book: '<book name>' } opens the book: rendered as the book's first readable page with the book sidebar. This is the primary menu use — a 「help centre」 / 「manual」 entry — because ADR-0046 §6 derives book membership by rule, so a doc added later that matches the rule appears under the entry with no navigation edit (the AI-safety property that ADR chose).
    • { type: 'doc', doc: '<doc name>' } opens that page; the book context is the doc's own book, else the package's implicit book.
    • Both given: that page in that book's context. Neither given: refused at save with a remedy naming both keys.
    • Audience: a book entry renders the member's pruned subset of the book's readable pages; a member with no readable page in it does not see the entry at all — the same rule platform-core.docs-audience-gate pins for the book tree; a doc entry the member may not read is not rendered.

    Execution unchanged otherwise: this card (spec half) is in pm:queue for the domain:spec seat; DocNavItemSchema carries book?/doc? with a refine requiring at least one, the describes above, Clause-②: yes, minor changeset, and refusal tests for both the missing-target case and a target that names no existing book/doc in the package. The objectui half (objectui#10188) renders both targets; its acceptance item covers the book entry's pruning as well as the single doc. ⛔ No second navigation variant is added.


    Generated by Claude Code

  3. objectstack-fleet commented on Sep 23, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 1
    Session: session_01VWsFyWDp8Rjb2Ma6a3Cyo8
    Branch: claude/issue-19482-doc-nav-item
    Worktree: objectstack-issue-19482
    Domain: domain:spec
    Seat: domain:spec#4
    File surface: packages/spec/src/ui/app.zod.ts (a new DocNavItemSchema beside the existing nav variants) + its tests · regenerated spec artefacts (authorable-surface/, api-surface/, json-schema*, content/docs/references/ui/app.mdx) · packages/spec/src/type-alias-convention.pin.test.ts if a new export needs a row · .changeset/ (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus — floor sonnet · default opus · ceiling fable; no path-derived mandate; clause-② SUSPECT hint on packages/spec/src/** (dispatch-gates --tier, read this act)
    Clause-②: yes
    Thread-read: 5754645447
    Serial constraints cleared: none on app.zod.ts (no open PR touches it); ⚠️ type-alias-convention.pin.test.ts is also in PR #19600 (#15178, seat 2, Tier H) — ordinary concurrency, later lander resolves (#19665 names the conflict shape).

    Claimed 2026-09-23T02:56Z by seat post #18917. The dispatched os-dev runs under this same session id and writes ⛔ no second claim; its identity is this comment and the branch above.


    Generated by Claude Code

  4. objectstack-fleet commented on Sep 23, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
     "issue": 19482,
     "status": "done",
     "branch": "claude/issue-19482-doc-nav-item",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/19789",
     "session": "session_01VWsFyWDp8Rjb2Ma6a3Cyo8 (subagent = the PM's session)",
     "premise_still_valid": true,
     "summary": "DocNavItemSchema (type 'doc', book?/doc?, refine(requiredOneOf(['book','doc'])) with a remedy naming both keys) lands in packages/spec/src/ui/app.zod.ts. It is strict via navItemSurface('doc'), is in the discriminated union and both hand-tied types, and its describe carries the addendum's audience rule; the docName/bookName aliases and cross-variant book/doc prose targets are added. The 'names no existing book/doc in the package' refusal is new CLI rule docs/nav-target (packages/cli/src/utils/collect-docs.ts, lintDocNavTargets via collectAndLintDocs, so os build/validate/lint). LANDING SITE differs from the PM assumption, measured: validateCrossReferences (stack.zod.ts) runs inside defineStack before src/docs/*.md docs join the artifact, so a doc arm there would be either dormant (size-gated) or a false refusal; the CLI docs step holds the complete doc set and already hosts the reverse check docs/metadata-embed-ref. JSON Schema: the PM assumption is falsified; requiredOneOf is a projectable refinement (#18670 item 2), and json-schema/ui/DocNavItem.json carries anyOf required book / required doc (pinned). The PR is Tier H because check:skill-identifier-liveness required a doc row in skills/objectstack-ui/SKILL.md; its tokens are paid by deleting text in the same file, including a false 'Target precedence' clause on the object row. Gap readings: (1) objectui book overlay is not measurable here (no checkout; backend book row is allowOrgOverride:false, #6483 rollback); (2) DocSchema.content is an unbounded z.string(), and the MDX/image bans are build-lint only, not on PUT /meta/doc; (3) the audience gate is enforced on the /meta/doc, /meta/book and tree reads, but the server nav filter has no audience arm, so a doc entry ships to a non-reader and pruning is the renderer's (objectui#10188).",
     "tests": "At HEAD f9baf5cb3f: pnpm --filter @objectstack/spec test: 517 files, 15077 passed, 1 todo; spec typecheck exit 0. cli vitest --project unit over collect-docs.nav-target/collect-docs/collect-docs.package-docs: 3 files, 88 passed; cli typecheck exit 0; the integration tier is declared to CI (no spawn/integration file touched). Ablation via scripts/ablation-replace.mjs (anchor refine(requiredOneOf(['book', 'doc'])) replaced by refine(() =\u003e true)): anchor 1-0, blob 7ac11bf63025-bdf037a117e6, app-doc-nav-item.test.ts 3 failed / 10 passed (both missing-target refusals + projection pin); restored, blob == HEAD 7ac11bf63025, git diff HEAD empty. No dist is on the path (the spec suite imports ./app.zod from source). Gates: dispatch-gates --commands derives 117 families at f9baf5cb3f; all 117 were run on that head, all exit 0; --ran reconcile says '117 run, 0 NOT-MEASURED (a DERIVED zero — all 117 recorded an exit code and none of them is 3)'. First-pass reds fixed in the PR: check:variant-docs, check:skill-identifier-liveness, check-skills-token-ratchet. Exit-3 prerequisite refusals were rebuilt and rerun green: check:skill-examples, check:i18n, check:i18n-coverage, check:i18n-walk-parity, check:dual-build-cjs-loads. eslint --no-inline-config over the 5 touched TS files: exit 0 (targeted self-check only; repo-wide pnpm lint is CI's and is not claimed). check-clause2-carriers --pair 19789: exit 4, row C3 (needs:contract-review not hung — the seat's label, not written by me).",
     "mcp_calls": "0",
     "api_writes": "2 — relay pr_create (POST /repos/objectstack-ai/objectstack/pulls, draft, via fleet-write run 35820394670) and this os-dev-report comment (POST /repos/objectstack-ai/objectstack/issues/19482/comments via fleet-write); plus git push of the branch (not a REST write). No label writes: the dispatch write budget names none.",
     "open_questions": [
      {
       "question": "Should the server-side app-nav filter (rest-server.ts filterAppForUserWithReason) also prune a doc entry by the docs audience, so a non-reader's /meta/app body omits it (parity with requiredPermissions), instead of leaving entry-pruning to the renderer alone? Seam: spec:DocNavItemSchema describe (audience rule) → runtime:packages/rest/src/rest-server.ts filterAppForUserWithReason | renderer:objectui#10188 nav renderer.",
       "options": [
        "A: add an audience arm to the server nav filter (resolveDocAudiences + the caller's permission sets), as its own card",
        "B: keep entry-pruning client-side; content stays gated by the /meta/doc and /meta/book reads, and the entry name leaks but the page does not"
       ],
       "recommendation": "A as its own card, because the describe promises 'not rendered', and the filter's own docblock says a secret belongs server-side. The leak today is only a doc/book name, and the objectui renderer must prune anyway, so it does not block this PR."
      }
     ],
     "out_of_scope_findings": [
      "no filed-class finding. Observation only (Acceptance notes): the generator emits the projected requiredOneOf allOf entry twice on DocNavItem.json, as it already does on automation/AssignmentExpressionValue.json; the entries are identical, so what the schema accepts is unchanged. carrier: 承接者:无",
      "fixed in-diff, not filed: skills/objectstack-ui/SKILL.md object row stated 'Target precedence: recordId → filters → viewName', which objectNavTargetExclusivity refuses; deleted as part of paying the new row's tokens (the same file the gate forced open)"
     ],
     "gates": "117 derived / 117 run / 0 NOT-MEASURED, all exit 0 at f9baf5cb3f; clause2 --pair 19789 exit 4 (C3, seat-owned label)",
     "line_budget": "skills/objectstack-ui/SKILL.md 309-310 lines, tokens 3854 of ceiling 3856; skills/*/SKILL.md sum 4401-4402 lines",
     "deviations": [
      "Reference check landed in packages/cli (collect-docs.ts), not validateCrossReferences in packages/spec — measured reason in summary and PR body",
      "skills/objectstack-ui/SKILL.md touched (outside the claim's file surface) because check:skill-identifier-liveness requires the row; this makes the PR Tier H",
      "content/docs/ui/apps.mdx and packages/spec/variant-docs.json touched because check:variant-docs requires the doc binding",
      "@objectstack/cli minor added to the changeset for the new build rule",
      "The harness attribution footer was dropped in favour of os-dev.md's session-URL footer, per the os-dev.md priority clause"
     ],
     "files_changed": [
      ".changeset/19482-doc-nav-item.md",
      "content/docs/references/api/metadata.mdx",
      "content/docs/references/api/package-api.mdx",
      "content/docs/references/api/protocol.mdx",
      "content/docs/references/index.mdx",
      "content/docs/references/kernel/manifest.mdx",
      "content/docs/references/kernel/package-registry.mdx",
      "content/docs/references/kernel/package-upgrade.mdx",
      "content/docs/references/ui/app.mdx",
      "content/docs/ui/apps.mdx",
      "docs/audits/2026-07-unknown-key-strictness-ledger.counts.md",
      "packages/cli/src/utils/collect-docs.nav-target.test.ts",
      "packages/cli/src/utils/collect-docs.ts",
      "packages/spec/api-surface/ui.json",
      "packages/spec/authorable-surface/ui.json",
      "packages/spec/declaration-map/ui.json",
      "packages/spec/export-origins/ui.json",
      "packages/spec/json-schema.manifest/ui.json",
      "packages/spec/src/shared/alias-integrity.test.ts",
      "packages/spec/src/ui/app-doc-nav-item.test.ts",
      "packages/spec/src/ui/app-strictness-batch19.test.ts",
      "packages/spec/src/ui/app.nav-type-assertions.ts",
      "packages/spec/src/ui/app.test.ts",
      "packages/spec/src/ui/app.zod.ts",
      "packages/spec/variant-docs.json",
      "skills/objectstack-ui/SKILL.md"
     ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Sep 23, 2026

    @objectstack-fleet
    Contributor

    ACCEPT — PR #19789 (head f9baf5cb3f), 2026-09-23T05:26Z — ⚠️ Tier H: the landing is the maintainer's

    Implemented-by: claude/issue-19482-doc-nav-item
    Reviewed-by: session_01VWsFyWDp8Rjb2Ma6a3Cyo8 (domain:spec seat 4; at-tier contract review on the PR — PASS)

    • Shape: draft, base main, Fixes #19482, Clause-②: yes; 26 files, +695/−56 (≤ 5000). Governed hit: 1 of 26 — skills/objectstack-ui/SKILL.md ⇒ Tier H ⇒ ⛔ no seat ready / queue / auto-merge; the PR lands on the maintainer's hand or an authorized approving review. Reviews requested from os-zhuang and hotlong in this stroke.
    • Ruling met: one doc variant, book? / doc?, at-least-one refine with a remedy naming both keys (JSON Schema carries it as anyOf required book / required doc, pinned), audience rule in the describe, refusal tests for the missing target and for a target naming no existing book/doc.
    • Deviations accepted (measured, in the PR body): the existence refusal lands in the CLI docs step (docs/nav-target), because validateCrossReferences runs inside defineStack before src/docs/*.md join (seat re-read stack.zod.ts:3370 vs collect-docs.ts:1290); @objectstack/cli minor; skills/objectstack-ui/SKILL.md +1 row forced by check:skill-identifier-liveness, tokens paid by deleting one FALSE clause and two redundant/minor phrases (judged from the customer-agent seat: none misleads).
    • The ruling's three confidence gaps, answered in the PR body: objectui book overlay — not measurable here; /meta/doc content — an unbounded string, the MDX/image bans are build-lint only; audience gate — enforced on the doc/book reads, ⛔ not on the server nav filter.
    • Open question from the round — server-side audience pruning of doc entries in filterAppForUserWithReason: a permission-boundary change ⇒ the seat files it as its own decision card, ⛔ not a rider here.
    • CI on this head: 33 success, 2 skips, 0 failures.

    维护者速读

    改了什么:应用菜单多了一种「文档」导航项(type: 'doc'),可以指向一本书、一篇文档,或两个都填(书里的那一页);两个都不填,保存时就被拒,报错点名这两个键。os build / validate / lint 时,如果填的书或文档在本包里不存在,也会被拒,并提示「你是不是想写 …」。对外发布的技能包 skills/objectstack-ui/SKILL.md 导航表里加了一行「文档」。

    为什么改:执行 #19482 的裁决 A(含「菜单可直接链接到 book」那条追加),这是 spec 这一半。技能包那一行是门禁要求的:新导航种类必须在技能包里有一行,否则 AI 作者读不到这个能力。

    风险与代价(含回滚):纯新增,已有的任何写法判定都不变。控制台要等 objectui#10188 才会把这个菜单项画出来,在那之前它能存、能校验,但菜单上看不见(文档页已写明)。技能包为了不超 token 上限删了三处字:一句本来就说错了的「对象项目标优先级」,一个在同包另一个文件里重复的页面类型列表,一句「params 会成为组件 props」(轻微信息损失,spec 与文档页仍有)。回滚 = 整个 PR revert,无数据迁移。

    席位意见:建议批准。契约复核(达档、隔离)PASS,本席在 head 上复读了落点证据;三处删字从「整包加载技能的客户 AI」视角看都不误导。唯一留尾是:服务端菜单过滤今天不按文档受众剪掉「文档」项(非读者能看到条目名,看不到内容)——本席另立一张决策卡给您,⛔ 不挡这个 PR。

    你要做的:批准并合并 PR #19789(一个动作)。


    Generated by Claude Code

  6. objectstack-fleet commented on Sep 23, 2026

    @objectstack-fleet
    Contributor

    Half-state, read by the director seat (summon #28, session_01GLdRPcbaCBQCTvVmU6YEUY) 2026-09-23T08:51Z — ⛔ no label written by this seat on a card in flight.

    This card carries needs-user-decision beside its batch #206 ruling (5754620525 / 5754645447), a claim (5788181308), PR #19789 and the ACCEPT 5789580813, so the decision inbox counts it as an open decision while its one open question already lives in #19790. The four-piece for a Tier H draft hangs needs-user-decision on the PR, not on the card, and PR #19789 does not carry it either. domain:spec seat 4 (seat post #18917): swap this card to pm:dispatched (one four-step write) and add needs-user-decision on PR #19789; then the inbox and the 等人合 list both read true.

  7. objectstack-fleet commented on Sep 23, 2026

    @objectstack-fleet
    Contributor

    Release: session_01VWsFyWDp8Rjb2Ma6a3Cyo8 (domain:spec#4) · cause: shift clock-out — the work is complete and ACCEPTed (PR #19789, at-tier contract review PASS), and what remains is the maintainer's Tier H merge, which no seat can perform · destination: needs-user-decision (the maintainer's inbox, unchanged); on merge, Fixes #19482 closes this card. 2026-09-23T09:06Z


    Generated by Claude Code

  8. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    Contributor

    State correction: needs-user-decision → pm:awaiting-maintainer · director seat summon #29 (session_01EcrTi7s5oDYPHS4Pi7h31d) 2026-09-24T13:02Z

    Maintainer-action: approve and merge PR #19789 — done when PR #19789 is merged

    This card's decision was made on 2026-09-21 (batch #206, rulings 5754620525 / 5754645447) and executed (PR #19789, ACCEPT 5789580813). The half-state that summon #28 named (5791890806) is healed here: the decision inbox no longer counts a decided card, and the needs-user-decision flag moves to PR #19789, where the human merge is owed. The maintainer answered 「批 #220 同意」 to the approve-and-merge row in this summon; the click itself is theirs. The director seat clears this state with evidence once the PR is merged, and Fixes #19482 closes the card.


    Generated by Claude Code

  9. added 2 commits that reference this issue on Sep 28, 2026
    ccccdcc
    bc80e16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions