Skip to content

skills(ui): the page skill learns the print page — print, the printable block subset and its lint (#22271) - #22287

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-22271-ui-skill-print-page
Oct 8, 2026
Merged

os-zhuang merged 2 commits into
mainfrom
claude/issue-22271-ui-skill-print-page

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #22271

Clause-②: no

The Studio author's page skill (skills/objectstack-ui/rules/pages.md) learns the print page that card ① of the ruling of record on #8346 (comment 6051470224, letter B′, maintainer 「8346 B′」) landed as #22158 through PR #22193: a printable document is a page that declares print; the eight print keys and what each means on paper; the three page types that may carry it; the printable block subset taught by reason, not as a copied list; the print-page-block-unprintable refusal; and the two list facts (list export has no 'pdf'; a list prints through the view's allowPrinting). #8346, #22158, #22193 and #22184 are references — none of them is addressed here.

Every sentence is true of origin/main at c8bb3c8d9c (this branch's base): the declaration is parsed and refused by @objectstack/spec (PagePrintSchema, checkPagePrintComposition) and by @objectstack/lint (validatePrintPageBlocks, rule print-page-block-unprintable, on os validate / os build / os lint and the page save door). What renders it is the console's print rendering — card ② of the same ruling, not on this tree — so the skill says in one clause that the rendering follows and that os validate warns the author today (the liveness row print is planned with authorWarn, packages/spec/liveness/page.json). No rendering is invented.

What changes (three markdown files, nothing else)

  • skills/objectstack-ui/rules/pages.md — a new ### Print pages section under Pages: what a print page is and the refused page kinds/types (list → interfaceConfig.allowPrinting, utility, slotted / html / react, a full page with no regions); the keys paperSize, orientation, margins (millimetres), repeatHeader / repeatFooter (the page's own header / footer regions; refused without that region), pageNumbers, repeatTableHeaders (on by default), avoidBreakInside; the printable subset stated as the property every member has (draws all it declares, in full, the same at any width) with the 13 members grouped by that property; the four refusal reasons with the blocks an author reaches for first (record:related_list / object-grid → record:line_items; page:header, sidebars, boards; record:path, tabs, buttons, inputs, forms → record:details; flex / object-chart / plugin widgets are outside the vocabulary); the rendering status in one clause; the two list facts; and one os:check invoice example (type: 'record', print with margins / running header and footer / page numbers, element:text letterhead, record:details, record:line_items, a footer region).
  • skills/objectstack-ui/SKILL.md — one word in the Detailed Rules routing row for pages.md ("print pages"), so an author routing from the entry file finds the section.
  • skills/objectstack-ui/rules/actions.md — one sentence beside the OpenInvoicePdfAction example: a printable document is a page that declares print (link to the section), validated today, rendered once the console's print rendering ships. The example itself is untouched (with only card ① landed, a per-project endpoint is still the only working route to a PDF today). Paid by deleting the three "Best practices" bullets the file already states elsewhere: confirmText for destructive actions (line 20), visible to hide an action (line 18), input.selectedIds for bulk actions (the list_toolbar row and the AddToCampaignAction example); the refreshAfter bullet, which had no other home, stays.
  • skills/objectstack-ui/evals/*.json — untouched: no eval quotes a changed line. Eval 4's expected_output names the record-page lessons (page:header with properties.actions, record:path with statusField / stages) that now live in the component catalogue rows.

Ratchet route and readings (scripts/check-skills-token-ratchet.mjs, convention ceil(utf8 bytes / 4))

Why the section lives inside pages.md and not in a new file. The ratchet admits no new file on an author's own: a discovered authored file with no ceiling is RED, and its remedy carries the maintainer-only marker — "adding a CEILINGS row prices new text into the bundle that ships to every customer project, which is the maintainer ruling this ratchet implements — not a step an author takes while landing the file." So the section is paid inside pages.md by deletion, with no re-wrap counted and no ceiling moved.

file lines before → after tokens before → after ceiling headroom after
skills/objectstack-ui/rules/pages.md 453 → 434 (−19) 5679 → 5691 (+12) 5692 1
skills/objectstack-ui/SKILL.md 308 → 308 (0) 3844 → 3847 (+3) 3856 9
skills/objectstack-ui/rules/actions.md 207 → 206 (−1) 2104 → 2101 (−3) 2108 7
whole package, all skills/*/SKILL.md (10 files) 4408 → 4408 (0) 52572 → 52575 (+3) — —
whole skills/objectstack-ui authored bundle (9 priced files) 2051 → 2031 (−20) 26096 → 26108 (+12) — —

Measured on c8bb3c8d9c (before) and on this PR's head (after); node scripts/check-skills-token-ratchet.mjs at the head: pages.md is 5691 tokens (ceiling 5692; headroom 1), SKILL.md … 3847 (ceiling 3856; headroom 9), actions.md … 2101 (ceiling 2108; headroom 7), exit 0.

Every deletion in pages.md, and where its content survives:

deleted survives in
"Pages let designers assemble record details, home pages, app launchers, and utility bars without writing React." the Page Types table (record / home / app / utility rows)
the whole ### Example — Record Detail Page os:check block (52 lines) the definePage + regions shape → the new invoice os:check block; page:header title / subtitle / actions → its catalogue row (extended); record:highlights properties.fields → its catalogue row (extended); record:path statusField / stages → its catalogue row (extended); the inlined ConvertLeadAction → rules/actions.md (same action, same skill); template: 'three-column' → Templates & Regions
the relative-date placeholder list and the DATE_MACRO_TOKENS pointer in the Variable substitution note SKILL.md → Date Macros — Filter Placeholders (the pointer stays, shortened)
the "Actions in header" note the page:header catalogue row ("ids of the bound object's actions, never a sibling action node")
react tier: "Compose layout with inline style (real CSS); use the injected blocks for data. Do NOT use Tailwind className — see Styling a page below" Styling a page → the kind:'react' bullet (inline style object) and the section's no-className rule
react-blocks note: "It is the authoritative answer to what props does ObjectForm / ListView take — author against it, not from memory." the same note's bold lead "Do not guess props — read the contract."
react-blocks note: "(Maintainers: regenerate with pnpm --filter @objectstack/spec gen:react-blocks.)" a maintainer process note, not customer teaching — AGENTS.md's regeneration table and packages/spec/package.json
"See ADR-0065 (SDUI styling model)." the section heading "Styling a page (ADR-0065)"
the src/docs/ tree diagram Authoring rule 2 (filename stem = doc name) and Routing model (/docs/ + name)
"A platform-level Documentation portal … is a later, additive concern — author-side, nothing to model now." the one deletion that is not a restatement: a roadmap remark; no authoring instruction is lost (there is no portal key to author) — flagged here for the reviewer
"Live instances vs. structural views", second half (structural metadata → a metadata fence) Inline metadata views — the metadata fence (the three-kinds table and its lint)
"Scope is deliberately narrow: only state_machine, flow, permission." the same section's "Three view kinds" table and the lint line ("type must be one of the three")

Controls (against the built @objectstack/spec dist at the head, scripts/pm/os-verify-lock.sh VERDICT command-exit 0)

  • Positive: the invoice example exactly as pages.md carries it → PageSchema.safeParse success = true, print parsed as {"paperSize":"A4","margins":{"top":15,"right":12,"bottom":15,"left":12},"repeatHeader":true,"repeatFooter":true,"pageNumbers":true}; validatePrintPageBlocks({ pages: [example] }) → [].
  • Refused by the lint rule (the same page holding unprintable blocks): rule print-page-block-unprintable, severity error, each with its reason — "page:header cannot be placed in a print page … it lays its action bar out to the screen width"; "record:related_list … draws only the first limit related records (5 unless set) … a window of the rows"; "flex … is not in the printable block subset, and nothing answers for how it prints"; "record:path … is an interactive stage control; the current stage is a field value, which record:details prints".
  • Refused at the parse: print on a type: 'list' page ("… set interfaceConfig.allowPrinting: true; to print a document, declare print on a page of type: 'record', 'home' or 'app' with its blocks in regions"); repeatHeader: true with no header region ("Add a { name: 'header', components: [...] } region holding the letterhead blocks, or delete repeatHeader"); paperSize: 'A3' ("expected one of A4 | A5 | Letter | Legal"); print on a utility page; print on a kind: 'react' page.
  • pnpm --filter @objectstack/spec run check:skill-examples at the head: "262 prose examples type-check across 3 surface(s) — every marked block parsed, so tsc ran the SEMANTIC pass on all of them", exit 0 (the new invoice block is one of the 228 skills + docs blocks).

Gates (derived on this tree by node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 24 families; every exit code captured before any pipe; --ran reconciliation: 24 accounted, 0 NOT-MEASURED)

command exit
node scripts/check-skills-token-ratchet.mjs (+ --self-test) 0 / 0
pnpm --filter @objectstack/spec run check:skill-docs 0
pnpm check:skill-identifier-liveness · check:skill-compatibility · check:skill-frame-sync 0 · 0 · 0
pnpm check:doc-authoring · check:corpus-claim-drift · check:nul-bytes 0 · 0 · 0
node scripts/check-doc-route-spelling.mjs --advisory (+ --self-test) 0 / 0
pnpm --filter @objectstack/lint run check:doc-formula-expressions 3 before the lint/formula build (prerequisite, not a verdict) → 0 after turbo run build --filter=@objectstack/formula --filter=@objectstack/lint --filter=@objectstack/client-react --filter=@objectstack/client (34/34 tasks)
node scripts/check-ci-filter-parity.mjs · check-closing-keyword-parity.mjs (+ --self-test) · check-comment-mask-corpus.mjs 0 · 0 / 0 · 0
pnpm check:agent-test-spelling · check:cross-package-test-inputs · check:driver-memory-census · check:gitlink-declared · check:pm-governed-merges · check:refd-timer-probe · check:role-word · check:watch-hint-literal all 0

Not run locally, CI's own: the type-check lanes and pnpm lint (no TypeScript or source file moves). No changeset: the diff publishes nothing from a released package (skills/** is not in any package's files), so skip-changeset is the declaration.

Acceptance notes (not filed; carrier named)

  • packages/spec/src/ui/app.zod.ts:108 declares a doc navigation-item type ({ type: 'doc', book } / { type: 'doc', doc }) and SKILL.md's nav table lists it, but pages.md → Routing model still says "There is no dedicated doc nav-item type yet, so use a url item", and eval 4's expected_output expects a type: 'url' nav item. A stale line in the published skill; a different defect class from this card, so not touched here. Carrier: the domain:skills seat, reported in this card's os-dev-report for filing.
  • The print-page example omits template (spec default 'default'); whether a print page constrains template is card ②'s call (PR feat(spec)!: a page gains an optional print declaration and a linted printable block subset; the zero-reader document schemas retire whole (#22158) #22193's own note). The skill does not speak about it.
  • Rendering: the skill's one clause ("the console's print rendering that applies it follows") is the only forward-looking sentence; the day card ② lands at a .objectui-sha pin, that clause and the os validate warning sentence are the two lines to revise.

维护者速读(草稿)

改了什么 — 给 Studio 作者技能的页面规则(skills/objectstack-ui/rules/pages.md)新增一节「Print pages」:可打印文档(发票、送货单、信函、报表)就是声明了 print 的普通页面,没有模板类型;八个 print 键各自对应纸面上的什么;只有带 regions 的 record / home / app 页面可以声明;打印页里只允许「整页画全、任意宽度布局不变」的块,其余块被 print-page-block-unprintable 拒绝并给出原因(只画一窗行、按屏幕布局、无可打印内容、词汇表之外);列表导出没有 'pdf',列表靠视图的 allowPrinting。附一个会被 check:skill-examples 类型检查的发票页示例。SKILL.md 路由行加「print pages」一词;actions.md 的「打开发票 PDF」示例旁加一句指向打印页。

为什么改 — #8346 裁决 B′ 原话:「the Studio author's skill learns the print page when ① lands」;① 已随 PR #22193 落地。spec 与 lint 会响亮拒绝写错的打印页,但没学过这个声明的 AI 作者根本不会去写它,只会继续给每个项目手搓一个 PDF 接口;技能文本是 npx skills add 直接发到每个客户项目的那份。

风险与代价(含回滚) — 发布技能包有 token 棘轮:pages.md 只剩 13 token 余量,新节靠删除同文件里重复的内容付账(记录详情页示例整块删除、其教学点并入组件目录表格;另 11 处重复句,逐条列在上文表格里,唯一一条非重复的是「文档门户是后续事项」这句路线图备注),净 +12 token,落在 5691/5692;SKILL.md +3、actions.md −3,三个上限都没动。风险一:AI 作者看到打印页示例,把 print 加到普通页面 —— 文本已写明只有文档才声明,spec 也会拒绝不合格的页面;风险二:今天没有渲染器读 print(裁决的卡 ② objectui 未落地),技能用一句话如实说明「已校验、渲染随后」,os validate 也会提示作者。回滚:整个 PR 只动三个 markdown 文件,revert 即可,无代码、无 changeset。

席位意见 —

你要做的 — 核对「Print pages」一节的措辞与上文的删除清单;认可则 APPROVE 这个 draft PR,由你或授权审批落地(skills/** 为 Tier H)。


Generated by Claude Code

objectstack-fleet Bot and others added 2 commits October 8, 2026 11:52
…able block subset and its lint

A printable document is a page that declares `print`; teach the keys and
their paper meaning, the three page types that may carry it, the printable
subset by reason, the `print-page-block-unprintable` refusal and the two list
facts (no `'pdf'` export; `allowPrinting`). Paid inside `pages.md` by deleting
the Record Detail example (its lessons move into the component catalogue
rows) and restated lines; one routing word in SKILL.md; one pointer sentence
in actions.md paid by its restated best-practice bullets.

Claude-Session: https://claude.ai/code/session_01CXydFDyiQwNbGFkmwrcRQq
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation labels Oct 8, 2026
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 8, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 4bfaf6efc2febe80b78dcedf57b7e3920f2a5aef
Local-runs: none

Reviewed in-seat by the skills seat 1 (dispatching seat; this session is served at the contract-review tier, read off get_session), 2026-10-08T12:23Z. Face hit: published skill text (skills/objectstack-ui/**, Tier H; dispatch-gates --tier MANDATORY). Read-only shape held: the diff (3 files, +71/−92, two commits), the card #22271 and its thread (the claim 6058929757, the os-dev-report 6059499743), the head's check-runs; nothing built or run locally. Premises re-read on origin/main fbcbcf124f (it contains the branch base c8bb3c8d9c; skills/objectstack-ui/** is untouched between them; merge-tree clean).

① Derived judgments

  1. Every print fact is read off packages/spec/src/ui/page.zod.ts on origin/main: PagePrintSchema holds the eight keys the section lists, with the defaults it states (paperSize A4 when omitted, orientation portrait, margins in millimetres, repeatHeader / repeatFooter off and refused without the region, pageNumbers off, repeatTableHeaders on when omitted, avoidBreakInside off); checkPagePrintComposition refuses print on the slotted / html / react kinds, on type: 'list' naming interfaceConfig.allowPrinting, on utility, and on a full page with no regions; PRINTABLE_PAGE_COMPONENT_TYPES is the 13 blocks the section names, grouped as it groups them. Correct.
  2. The four refusal reasons compress PRINT_REFUSED_PAGE_COMPONENT_TYPES faithfully: a window of rows (record:related_list, object-grid), laid out to the screen (page:header, sidebars, kanban / calendar / gantt / map), nothing to print (record:path, buttons, inputs, forms), outside the vocabulary (the lint's fallback for flex, object-chart, plugin widgets). One grouping is looser than the source: tabs and accordions are refused because a closed panel never reaches paper, which the section files under "nothing to print"; the refusal and the remedy the author needs are the same. Accepted as teaching text; flagged under ③.
  3. Rendering truth: packages/spec/liveness/page.json row print is planned with authorWarn (validated, nothing applies it; card ② of the export: PDF / print document generation — re-planned for v18, superseding the #1301 NOT_PLANNED closure #8346 ruling is the carrier). The section states this in one clause and invents no renderer; actions.md keeps the per-project PDF endpoint example and adds one pointer sentence. List facts: exportOptions refuses a retired 'pdf' (view.zod.ts, spec: ComponentPropsMap['object-grid'].exportOptions is z.unknown(), so a bare exportOptions: ['csv'] passes every door and objectui's grid silently exports csv/json instead #21229) and a list prints through allowPrinting. Correct.
  4. The invoice example is an os:check block whose blocks are all in the printable set and which declares the header and footer regions its two repeat keys need; the dev's control parsed it (PageSchema.safeParse success, validatePrintPageBlocks empty) and check:skill-examples type-checked 262 blocks at the head (exit 0). The seat did not re-run it. Consistent with the schema the seat read.
  5. Ratchet, recomputed by the seat from the head blobs (ceil of utf8 bytes over 4): pages.md 22762 bytes = 5691 tokens (ceiling 5692), SKILL.md 15386 = 3847 (3856), actions.md 8404 = 2101 (2108); no ceiling row changed; the new-file route is refused by the ratchet's own MAINTAINER-ONLY remedy, so the section is paid inside pages.md. Each deletion survives where the PR body says — the seat checked three-column (Templates & Regions), the className rule (Styling a page), the extended page:header / record:highlights / record:path catalogue rows, convert_lead with confirmText / visible / selectedIds (actions.md), the Date Macros pointer, rule 2's filename stem and the /docs/ routing, the three-kinds table, and AGENTS.md's gen:react-blocks row. One deletion is not a restatement: the "Documentation portal is a later concern" roadmap remark — no authoring instruction behind it; flagged for the approver in the brief. No link elsewhere in the tree points at the removed #example--record-detail-page anchor (grep over skills, content, docs: 0). Correct.
  6. Scope: git diff --stat base..head = the three claimed markdown files; no eval changed, and eval 4's expected_output (three-column, page:header ids, record:path statusField / stages) still finds its lessons in the catalogue rows. 24 gate families derived, each exit captured, --ran reconciliation 24 derived / 24 run / 0 NOT-MEASURED; two exit-3 prerequisites re-run to 0 after the closure build under the verify lock. The CI reading at this record's clock is in the ACCEPT on skills: the Studio author's page skill learns the print page (page.print, the printable block subset, its lint) now that #22158 has landed — #8346 ruling B′ #22271.

② Semver level

None. Published skill text; no package content, API or schema. skip-changeset is the correct declaration, applied by this seat after its own reading (the dev's label-write was refused by its session classifier and recorded in its report).

③ Boundary flags

  • Dev flag: label-write refused by its classifier ("External System Writes") — recorded; the label and assignee are the seat's own act.
  • Seat note (judgment 2): tabs / accordion sit under "nothing to print" where the source's reason is "a closed panel never reaches paper"; same refusal, same remedy; a one-word fix rides the next edit of the section (headroom 1 token today).
  • Dev finding (class b, this lane): pages.md Routing model says there is no doc nav-item type and eval 4 expects a url item, while app.zod.ts:108 declares type: 'doc'; the seat files it as a card of this lane — not this PR's.
  • Forward-looking lines (two in pages.md, one in actions.md) are the ones to revise when card ② lands at a .objectui-sha pin; the carrier is named in the PR body.
  • open_questions: none; premise_still_valid: true.

Implemented-by: claude/issue-22271-ui-skill-print-page
Reviewed-by: session_01CXydFDyiQwNbGFkmwrcRQq

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读(终稿)

席位:skills 席 1,session_01CXydFDyiQwNbGFkmwrcRQq,2026-10-08T12:28Z。对照席位自己读的 diff(head 4bfaf6efc2,三个 markdown 文件 +71/−92)与 origin/main 上的 spec 源码(packages/spec/src/ui/page.zod.ts 的 PagePrintSchema、checkPagePrintComposition、两张块清单;liveness/page.json 的 print 行)校正 dev 草稿;契约复核记录(PASS)在本 PR 上一条评论,ACCEPT 在卡片 #22271。

改了什么:给 Studio 作者技能的页面规则(skills/objectstack-ui/rules/pages.md)新增一节「Print pages」:可打印文档(发票、送货单、信函、报表)就是声明了 print 的普通页面,没有模板类型;八个 print 键各自对应纸面上的什么;只有带 regions 的 record / home / app 整页可以声明(list 页走视图的 allowPrinting);打印页里只允许「整页画全、任意宽度布局不变」的 13 种块,其余被 print-page-block-unprintable 拒绝并给出原因;列表导出没有 'pdf'。附一个会被 check:skill-examples 类型检查的发票页示例。SKILL.md 路由行加「print pages」一词;actions.md 的「打开发票 PDF」示例旁加一句指向打印页,示例本身保留(卡 ② 未落地前,按项目自建接口仍是今天唯一能出 PDF 的路)。

为什么改:#8346 裁决 B′ 原话「the Studio author's skill learns the print page when ① lands」;① 已随 PR #22193 落地。spec 与 lint 会响亮拒绝写错的打印页,但没学过这个声明的 AI 作者根本不会去写它;技能文本是 npx skills add 直接发到每个客户项目的那份。

风险与代价(含回滚):发布技能包有 token 棘轮,且棘轮自己拒绝作者新增文件(加 CEILINGS 行是维护者专属动作),所以新节在 pages.md 内靠删除付账:整块删除「记录详情页示例」(其教学点并入组件目录表格三行)和 11 处重复句,逐条列在 PR 正文表格里,席位逐条核过落点。唯一一条非重复的删除是「文档门户是后续事项」这句路线图备注——后面没有作者指令,但请你知情。净 +12 token,落在 5691/5692(余量 1:下次改 pages.md 必须再删);SKILL.md 3847/3856、actions.md 2101/2108,三个上限都没动。风险一:AI 作者把 print 加到普通页面——文本写明只有文档才声明,spec 也拒绝不合格的页面;风险二:今天没有渲染器读 print(裁决的卡 ② objectui 未落地),技能用一句如实说「已校验、渲染随后」,os validate 也提示作者(liveness 行 planned + authorWarn)。回滚 = revert 落地的那一个 commit,无代码、无 changeset。

席位意见:同意合入,席内契约复核 PASS。席位自己核过:八个键与默认值、三种页型、四种拒绝形态、13 种可打印块与 spec 源码一致;删除的内容在 PR 正文说的位置都在;树上没有别处链接到被删的 #example--record-detail-page 锚点。一处措辞比源码松:tabs / accordion 被拒的原因是「收起的面板到不了纸上」,文本把它们归在「无可打印内容」一组——拒绝与补救相同,留待下次动这一节时顺手改(今天余量 1 token)。dev 顺带发现的旧错(pages.md 路由节说「还没有 doc 导航项类型」,eval 第 4 条也期望 url 项,而 spec 的 app.zod.ts:108 早已声明 type: 'doc')由本席在本车道另立卡,不在本 PR。卡 ② 落地时要回改的三句(两句在 pages.md、一句在 actions.md)已在 PR 正文点名。

你要做的:用授权账号(os-zhuang 或 hotlong)在本 PR 上 APPROVE 一次,或亲手合入。PR 保持 draft;收到授权 APPROVED 后由本席落地并收口 #22271(若批准者已自行翻 ready 入队,本席只做收口)。


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review October 8, 2026 12:33
@os-zhuang
os-zhuang enabled auto-merge October 8, 2026 12:33
@os-zhuang
os-zhuang added this pull request to the merge queue Oct 8, 2026
Merged via the queue into main with commit 238222d Oct 8, 2026
40 of 41 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-22271-ui-skill-print-page branch October 8, 2026 13:06
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/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

3 participants