Skip to content

skills(objectstack-ui): teach list-view grouping and orderBy, the duration measure, action copy holes, and a flow action's onSuccess - #26385

Merged
os-zhuang merged 1 commit into
mainfrom
claude/project-thread-5txv5f
Oct 27, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
claude/project-thread-5txv5f

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Warren · project thread
Fixes #26295
Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:objectstack-ui 技能包补上 18.2 的四条教学 —— 列表 grouping.fields[] 与 orderBy、数据集 duration 度量、动作文案的 {{ }} 占位(各键的根、bulk 的 count、四种拒收形态)、flow 动作的 onSuccess 读 ${result.output.NAME};配三条 eval。新增先在同文件删重复内容付账,余额按裁决 A 提高棘轮里该包五行的天花板。
  • 为什么改:AI 作者在 18.2 上按阶段分组列表、算平均成交时长、写带记录名的 toast、流程跑完后跳到新建记录,技能文档一条都没教(审计 skills: audit what the skills teach against the protocol since 18.0 — (A) published skills/ vs every added, changed or retired authorable key; (B) where pm-dispatch requires the skills/docs follow-up #26184 的 U1 / U2 / U4 / U5 行);其中 ${record.name} 写进 toast 会在 os validate 被拒。
  • 风险与回滚:只改技能 markdown、eval JSON 和 scripts/check-skills-token-ratchet.mjs 的五行数字;不发版、无运行时改动,回滚 = revert 本 PR。风险在教学措辞与 spec 是否一致 —— 三条新示例形状都用 zod 解析探针验过(见 Tests)。
  • 先看:① skills/objectstack-ui/rules/actions.md 的文案占位一行和 flow 示例的 onSuccess;② rules/list-views.md 新的 Grouping 节(select 按选项顺序、orderBy 只接数字/日期/布尔);③ scripts/check-skills-token-ratchet.mjs 五行天花板(+43 / −13 / −12 / +284 / +119)。
  • 两分钟验证:node scripts/check-skills-token-ratchet.mjs —— 全绿,objectstack-ui 的 actions / dashboards / list-views 与两个 eval 文件 headroom 都是 0;或 pnpm --filter @objectstack/spec check:skill-examples —— 两个改过的 os:check 示例编译通过。
  • 需要你做的:Approve(Tier H,skills/** 是发布面),席位随后落地。

What changes

Four rows of the audit on #26184 (comment 6425678185, § objectstack-ui: U1, U2, U4, U5), taught in the fewest lines an AI author needs, in the files the card names:

  • rules/list-views.md — a Grouping section (and its TOC entry): grouping.fields[] nests levels outermost first; a select level keeps its declared option order, so "stage in pipeline order" is { field: 'stage' } with the stages declared in that order; orderBy places a level's groups by a number, date or boolean field of the same object (the period-label case), and text or a select there is refused (list-view-group-order-unorderable).
  • rules/dashboards.md — the duration measure, in place: the Level A table's measures row names it, the Computed column bullet routes "time between two datetime fields of a record" to { aggregate: 'avg', duration: { from, to, unit } } instead of a hook-kept number, and the leave-days example says why it still escalates (date fields, so no duration). No parallel section.
  • rules/actions.md — one bullet on the copy slots: holes are {{ }}, confirmText reads record, successMessage reads record and result, errorMessage fills none, a bulkActionDefs entry's confirmText reads {{ count }} and its params by bare name, and ${record.name}, {name}, logic in a hole and a blank sentence fail os validate. The reassign example's toast now carries a hole ('Lead {{ record.name }} reassigned.'). The flow example gains onSuccess: { navigate: '/apps/crm/opportunity/record/${result.output.opportunity_id}' } and its heading says when the hop runs (a completed run, not one paused at a screen) and what ${result.output.NAME} reads (an isOutput variable).
  • evals/views-apps-actions-pages.json — eval 7 (group opportunities by stage in pipeline order, forecasts by period_label newest first) and eval 8 ("Lead NAME reassigned" toast + open the opportunity a flow created). evals/analytics-inline-vs-dataset.json — eval 8 (average days from created_at to closed_at). Same shape as the files' existing entries.
  • scripts/check-skills-token-ratchet.mjs — the five CEILINGS rows of the files above, moved to the measured counts (the ruling and arithmetic below).

Line readings (whole file, before → after, on base 756220c):

file lines tokens (ceil(utf8 bytes / 4), the sibling gate's unit)
rules/actions.md 206 → 203 2101 → 2151 (+50)
rules/dashboards.md 465 → 464 6251 → 6239 (−12)
rules/list-views.md 303 → 311 3133 → 3129 (−4)
package (SKILL.md + references/**/*.md + rules/**/*.md) 2053 → 2057 28253 → 28287 (+34)
evals/views-apps-actions-pages.json 65 → 85 1505 → 1789 (+284)
evals/analytics-inline-vs-dataset.json 77 → 87 1229 → 1348 (+119)
whole skills/objectstack-ui/ tree 2226 → 2260 ratchet bundle total 206107 → 206544 (+437)

Paid down first, then raised — the token ratchet. node scripts/check-skills-token-ratchet.mjs prices every file above per file, shrink-only, and on base every one of them sat at 0–8 tokens of headroom. The teaching alone costs +141 / +66 / +127 / +284 / +119. Each rule file pays in its own file by deleting restatements, every cut's surviving home named here:

  • actions.md (−91): the sentence that ActionRunner.executeUrl reads openIn with priority over a legacy heuristic (an implementation note; the author-facing rule is the paragraph it sat in); the "validated today, rendered once the console's print rendering ships" clause (home: rules/pages.md → Print pages, which carries that status); the params-callout's closing remedy that re-listed the two mechanisms (home: the two subsections directly above it; the callout now points there); "so it matches hooks and seed data with zero relearning" (home: the same paragraph's last sentence, "same blessed name … as hooks", and the code comment "identical to the hook surface"); the code comment "zero-roundtrip; endpoint self-auths" (home: the prose two lines above the block). Plus the card-named stale line 21 reworded ("author-controlled" dropped). Net +50; the ceiling absorbs its 7 of headroom and moves by the remaining +43.
  • dashboards.md (−78): the four-line comment inside the SalesDashboard example restating that widgets bind a dataset and select by name and that filter is the runtime filter (home: the Dataset-Bound Widgets section, which states all three). Net −12; the ceiling is lowered to the measurement.
  • list-views.md (−131): the paragraph after the allowed-set table that restated its two rows (home: the table, which the sentence above it already calls "the whole rule"); the related-record search paragraph, compressed to one sentence that points at objectstack-data → Search Fields, which carries the mirror field, the two hooks and why a formula cannot be the mirror in full (the dotted-path refusal stays in this file's own table row). Net −4; the ceiling is lowered to the measurement.
  • The two eval files are fixtures with nothing to delete; their rows are raised by the whole addition (+284, +119).

The ruling the raise runs on — Warren, 2026-10-27T01:21Z, option A tapped on the decision card 「skills 修复卡的教学和 eval 都撞 token 天花板,怎么付?」, verbatim and untranslated: 「A:先削重复,余额按实测提高」. Each row's comment in the script quotes it. Ceilings net +421 (+437 of growth, 16 of pre-existing headroom absorbed); every touched row is pinned at its measurement, zero headroom.

Why

An AI author building lists, dashboards and actions on 18.2 could not: group a list by stage in pipeline order (list-view grouping was untaught — 0 hits); keep "average time to close" on the dataset (the escalation advice sent every computed value to a hook-kept stored field, and never named the measure that now exists); write a toast that names the record ('Lead ${record.name} reassigned' reads as plain copy in the old text and is refused at os validate); or take the user to the record a flow created (onSuccess is read for type: 'flow' since the executor set widened, and the flow example lacked it).

Premise checks against origin/main 756220c (the order's hypotheses; the cited commits are unreachable in this shallow clone too — git cat-file -t answers missing for all nine — so the spec lines are the evidence):

  1. Holds, with one correction to the example. grouping.fields[].orderBy is packages/spec/src/ui/view.zod.ts:1441. But a select level already groups in its declared option order — the grid's group comparator ranks option values by the field's options (ui/packages/plugin-grid/src/useGroupedData.ts, compareGroupEntries; ObjectGrid.tsx builds groupOptionRanks), and orderBy over a select is refused by list-view-group-order-unorderable (packages/lint/src/validate-list-view-field-refs.ts:405; rule-explanations.ts:3417: min/max over the string classes is refused by AGGREGATE_FIELD_TYPE_COMPATIBILITY). So the pipeline-order case is taught as { field: 'stage' } with no orderBy, and the orderBy example is the spec's own fixture case, period_label ordered by period_start (view-grouping-query.test.ts:595). Eval 7 expects exactly that split.
  2. Holds. DatasetMeasureDurationSchema at packages/spec/src/ui/dataset.zod.ts:276 (from, to, unit ∈ seconds/minutes/hours/days, aggregates avg/min/max/sum, datetime columns only — DATASET_DURATION_FIELD_TYPE, line 247). Corrected in place at the Computed-column bullet; the leave-days example at lines 93–99 stays right because its fields are date.
  3. Holds. Roots and refusals per packages/spec/src/ui/action-copy-template.ts (actionConfirmTemplateRoots, ACTION_SUCCESS_TEMPLATE_ROOTS, ACTION_ERROR_TEMPLATE_ROOTS); bulk roots count + params by bare name per bulk-action.zod.ts:334–364. One nuance kept out of the teaching: a registered action's errorMessage is an I18nLabel, not a template slot (action.zod.ts:1424), and validate-action-copy-templates.ts judges errorMessage only on page action rows — "fills none" is true on both surfaces.
  4. Holds. onSuccess beside type: 'flow' (action-on-success-executor.ts:25–33; action.zod.ts:1719–1721), ${result.output.VAR} reading the flow's isOutput variables; the example's opportunity_id is declared isOutput: true by examples/app-crm/src/flows/convert-lead.flow.ts:47.
  5. As predicted: spec lines cited, not commits.
  6. Holds: 0 hits for orderBy / duration / onSuccess in the package's evals on base; all three evals are new.

Tests

No TypeScript changes; the gate that compiles the package's os:check examples and a zod parse probe of every new shape are the tests.

  • Zod parse probe (tsx, against packages/spec/src on this head; script in the dev's scratchpad, nothing committed):
    • ActionSchema.safeParse of the reassign example (successMessage: 'Lead {{ record.name }} reassigned.') → PARSES; of the convert example with onSuccess.navigate → PARSES; control, onSuccess on a type: 'url' action → REFUSED ("a url action is itself the navigation…").
    • closedScopeTemplateRefusal over the successMessage roots: 'Lead ${record.name} reassigned' → refused ("write each ${…} token as the {{ }} hole it names"); 'Lead {name} reassigned' → refused (single-brace dialect); 'Lead {{ record.name }} reassigned' → no refusal.
    • DatasetMeasureSchema.safeParse({ name: 'avg_days_to_close', aggregate: 'avg', duration: { from: 'created_at', to: 'closed_at', unit: 'days' } }) → PARSES; controls: aggregate: 'count' with duration → REFUSED ("a duration measure takes aggregate avg / min / max / sum"); field beside duration → REFUSED ("it takes no field").
    • GroupingFieldSchema.safeParse({ field: 'stage' }) → PARSES; { field: 'period_label', orderBy: 'period_start', order: 'desc' } → PARSES; control, padded orderBy: ' period_start' → REFUSED (the non-padded rule).
  • Both eval files load with json.load (shape unchanged: id / prompt / expected_output / files / assertions).
  • pnpm exec eslint --no-inline-config scripts/check-skills-token-ratchet.mjs → exit 0.
  • Ablation: n/a — no tested behaviour changed (docs, fixtures and a gate's pinned numbers).

Acceptance notes

  • The card's acceptance — an eval per new teaching; check:skill-docs, check:skill-refs, check:skill-examples green; both readings reported; additions paid inside the package; nothing removed beyond the stale lines the card names — is met as read above, with one widening the ruling ordered: the five CEILINGS rows in scripts/check-skills-token-ratchet.mjs, outside skills/objectstack-ui/**. The seat amends the claim's file surface for them.
  • The cuts delete restatements only; each has a surviving home named above. The card-named stale lines (actions.md:20-21, :64-69; dashboards.md:69-72, :90-92) are rewritten in place, not dropped.
  • references/_index.md is generator-owned and unchanged (check:skill-refs green); line 1 of SKILL.md is untouched (PR chore: version packages #25640 owns it).
  • Observed, not filed (os-dev rule 3, none of the three classes): rules/actions.md still says errorMessage is "toast copy" for a registered action, which is right; that a page action row's errorMessage is additionally judged as a template slot is an objectui-surface nuance with no author-facing choice. evals/README.md describes the two files generically; no edit needed.
  • skip-changeset: the skills packages carry no package.json and nothing in a released package's files[] moves; the ratchet script is repo tooling.

Verification

Gates derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on this tree (50 commands: 42 matched by path, 8 declared whole-tree), every exit code captured before any pipe; reconciled with --ran.

48 green, 2 NOT MEASURED (exit 3, prerequisite: a whole-tree build), 0 red, 0 unrun on head {HEAD}.

Card acceptance gates and the ratchet (exit codes captured before any pipe)
  • pnpm --filter @objectstack/spec check:skill-docs → exit 0
  • pnpm --filter @objectstack/spec check:skill-refs → exit 0 (references/_index.md in sync, unchanged)
  • pnpm --filter @objectstack/spec check:skill-examples → exit 0 — 264 marked examples type-check across 3 surfaces (230 skills + docs blocks, the two edited os:check blocks in rules/actions.md among them), after building @objectstack/client-react / @objectstack/client under the lock
  • node scripts/check-skills-token-ratchet.mjs --self-test → exit 0 (79 cases); node scripts/check-skills-token-ratchet.mjs → exit 0 — every objectstack-ui row green, the five touched files at headroom 0, bundle total 206544
Every other derived family, by exit code
  • node scripts/check-ci-filter-parity.mjs → exit 0
  • node scripts/check-closing-keyword-parity.mjs → exit 0
  • node scripts/check-closing-keyword-parity.mjs --self-test → exit 0
  • node scripts/check-comment-mask-corpus.mjs → exit 0
  • node scripts/check-declaration-mirrors.mjs → exit 0
  • node scripts/check-declaration-mirrors.mjs --self-test → exit 0
  • node scripts/check-doc-route-spelling.mjs --advisory → exit 0
  • node scripts/check-doc-route-spelling.mjs --self-test → exit 0
  • node scripts/check-dts-emitted.mjs --self-test → exit 0
  • node scripts/check-scripts-symbol-anchors.mjs → exit 0
  • node scripts/check-scripts-symbol-anchors.mjs --self-test → exit 0
  • node scripts/check-self-test-wired.mjs → exit 0
  • node scripts/check-self-test-wired.mjs --self-test → exit 0
  • node scripts/check-self-test-workflow-commands.mjs → exit 0
  • node scripts/check-self-test-workflow-commands.mjs --self-test → exit 0
  • node scripts/check-whole-set-label-write.mjs → exit 0
  • node scripts/check-whole-set-label-write.mjs --self-test → exit 0
  • node scripts/ci/assert-turbo-remote-cache.mjs --self-test → exit 0
  • node scripts/ci/prune-turbo-cache.mjs --self-test → exit 0
  • node scripts/pm/bare-root-worklist.mjs --self-test → exit 0
  • pnpm --filter @objectstack/lint run check:doc-formula-expressions → exit 0
  • pnpm check:agent-test-spelling → exit 0
  • pnpm check:bash32-floor → exit 0
  • pnpm check:cli-command-ids → exit 0
  • pnpm check:corpus-claim-drift → exit 0
  • pnpm check:cross-package-test-inputs → exit 0
  • pnpm check:doc-authoring → exit 0
  • pnpm check:driver-memory-census → exit 0
  • pnpm check:dts-closure → exit 0
  • pnpm check:entry-guard → exit 0
  • pnpm check:gitlink-declared → exit 0
  • pnpm check:nul-bytes → exit 0
  • pnpm check:parse-guard → exit 0
  • pnpm check:pm-dispatch-gates → exit 0
  • pnpm check:pm-governed-merges → exit 0
  • pnpm check:pm-skill-id-lint → exit 0
  • pnpm check:pnpm-filter-targets → exit 0
  • pnpm check:pnpm-flag-spellings → exit 0
  • pnpm check:ratchet-remedy-authority → exit 0
  • pnpm check:refd-timer-probe → exit 0
  • pnpm check:role-word → exit 0
  • pnpm check:skill-compatibility → exit 0
  • pnpm check:skill-frame-sync → exit 0
  • pnpm check:skill-identifier-liveness → exit 0
  • pnpm check:sourcemap-no-sources-content → exit 0
  • pnpm check:watch-hint-literal → exit 0

NOT MEASURED — the gate refused before measuring (exit 3), declared here, CI runs it:

  • pnpm check:dual-build-cjs-loads → exit 3 — reads every package's built dist/ (93 packages); CI's Lint & Repo Gates builds the tree first
  • pnpm check:lean-entry-closure → exit 3 — loads built entry points (@objectstack/objectql/core absent here); same CI family
Reconciliation, builds and the rest
  • node scripts/pm/dispatch-gates.mjs --ran gates/ran.list → exit 0 — ✓ dispatch-gates --ran: 50 derived famil(ies) accounted for — 48 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3).
  • Builds under scripts/pm/os-verify-lock.sh (slot issue-26295): pnpm --filter @objectstack/spec build VERDICT command-exit 0 (64 s held); turbo run build --filter=@objectstack/formula --filter=@objectstack/lint exit 0; turbo run build --filter=@objectstack/client-react --filter=@objectstack/client VERDICT command-exit 0 (133 s held).
  • pnpm check:pm-dispatch-gates → exit 0 (fast tier, 1901 cases), run detached as os-dev directs.
  • Not owed locally: no package test / typecheck (no TypeScript changed; dispatch-gates derives no type-check program for this diff); test:repo, pnpm lint and the whole-tree scans are CI's. Dogfood battery: n/a — no ! in the title and no (narrowing) arm.
  • CI on the PR: not waited for (the order forbids it); the seat reads the check-runs.

Generated by Claude Code

…ation measure, action copy holes, and a flow action's onSuccess

rules/list-views.md gains a Grouping section (grouping.fields[], a select
level's option order, orderBy for a number/date/boolean field); dashboards.md
names the duration measure where the escalation advice sent the time between
two datetime fields to a hook-kept number; actions.md teaches the {{ }} copy
holes with their roots and refused shapes, and the flow example gains
onSuccess reading ${result.output.NAME}. Three eval prompts cover the four
teachings. Additions are paid down first by deleting restatements in the same
files; the token ratchet rows for the five files move to the measured counts.

Claude-Session: https://claude.ai/code/session_01JQwxbXGPgrhvw7MBugmme8
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Reviewed at 2026-10-27T01:57Z — PR #26385 (card #26295, skills/objectstack-ui), independent contract review at the ceiling tier, read-only, adversarial to the dispatch order, the claim and the dev's report. Inputs: the card body and its four comments (re-fetched over REST at 2026-10-27T01:49Z, the claim amendment 6431429399 included), audit Part A rows U1/U2/U4/U5 on #26184 comment 6425678185, the PR body / file list / diff over REST, the spec and the four skills files on origin/main (2163e806) and on the head via git show, scripts/check-skills-token-ratchet.mjs on both, .github/workflows/lint.yml on origin/main, and the check-runs on the head.

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 17b18cdeabc2bbdd96321799c97bce8e3d5c7eeb
Local-runs: probe — wc -c over git show 17b18cde:PATH and git show 756220c4:PATH blobs to recompute the ratchet's own measure, ceil(utf8 bytes / 4) (script line 763, a pure function of the blob), for the five touched files and the package's .md set; nothing built, no gate re-run
Scope: skills/objectstack-ui/evals/analytics-inline-vs-dataset.json, skills/objectstack-ui/evals/views-apps-actions-pages.json, skills/objectstack-ui/rules/actions.md, skills/objectstack-ui/rules/dashboards.md, skills/objectstack-ui/rules/list-views.md + scripts/check-skills-token-ratchet.mjs (the sixth path of the diff: the gate the five rows price, read as one change with the files), skills/objectstack-ui/rules/pages.md (claimed home of the cut print-status clause), skills/objectstack-data/SKILL.md § Search Fields (claimed home of the cut related-record prescription and allowed-set paragraph), skills/objectstack-ui/SKILL.md and references/_index.md (untouched; read for references to the cut lines and the new section), packages/spec/src/ui/view.zod.ts, dataset.zod.ts, action.zod.ts, action-copy-template.ts, action-on-success-executor.ts, bulk-action.zod.ts, packages/spec/src/data/aggregate-field-type-compatibility.ts, packages/lint/src/validate-list-view-field-refs.ts, validate-action-copy-templates.ts, validate-dataset-measure-aggregates.ts, packages/spec/src/automation/flow-text-slot-template.ts, ui/packages/plugin-grid/src/useGroupedData.ts and ObjectGrid.tsx, ui/packages/app-shell/src/utils/flowResponse.ts, examples/app-crm/src/flows/convert-lead.flow.ts, .github/workflows/lint.yml, .github/PULL_REQUEST_TEMPLATE.md, .claude/agents/os-dev.md, eslint.config.mjs

Check-runs on 17b18cde, read at 2026-10-27T01:52Z: 50 total — 34 success, 16 skipped, 0 failure, 0 in progress (the 01:46Z reading had 12 in progress; the last four — Test Core 1/8, Lint · scoped · slot lookup & corpus, Type Check · source gates, Type Check · consumer gates — completed between 01:47Z and 01:52Z, and three aggregate runs appeared: Test Core, TypeScript Type Check, Lint & Repo Gates, all success). Lane mapping, corrected from the brief by reading lint.yml on origin/main: node scripts/check-skills-token-ratchet.mjs --self-test and the gate itself (lines 5589–5590), pnpm check:ratchet-remedy-authority (5582), check:skill-docs (5326) and check:skill-refs (5458) all run in Type Check · source gates; check:skill-examples runs in Type Check · consumer gates (6463). Both success. Lint · ESLint success (no max-len rule exists in eslint.config.mjs, so the long row-comment lines are not capped by anything). Check Changeset success with skip-changeset; the three claim/closing guards (The card this PR closes must claim this branch, Part-of PR must not also close its card, No other open PR may claim the same issue) success. The skipped 16 are the UI / docs / opt-in lanes the path filter skips for a diff with no ui/** or content/**.

① Derived judgments

(a) Every new sentence against the spec on origin/main — all true, two nits.

  • Grouping (rules/list-views.md § Grouping, new). "nests levels, outermost first" — GroupingConfigSchema's contract: groupBy = fields[].field in nesting order (view.zod.ts ~1480). "A select level keeps its option order … others sort by label" — the console's comparator (useGroupedData.ts compareGroupEntries, 341–380) ranks group keys by the field's declared options when ObjectGrid.tsx (2466–2480) builds groupOptionRanks from objectFields[field].options, and falls back to label order when the field declares none; the dev's premise correction (pipeline order = { field: 'stage' }, no orderBy) is right. "orderBy orders a level's groups by a number, date or boolean field of the same object — text or a select is refused (list-view-group-order-unorderable)" — the id is emitted by validate-list-view-field-refs.ts checkGroupOrder (1022–1058): it judges min (asc) / max (desc) over the field's declared type through AGGREGATE_FIELD_TYPE_COMPATIBILITY, whose min/max row is the numeric class (number, currency, percent, rating, slider, progress, summary), the temporal class (date, datetime, time) and the boolean class (boolean, toggle); text and select are outside it, and formula is refused by the virtual-field remedy. order: 'desc' is a real key (order: z.enum(['asc','desc']).default('asc'), view.zod.ts 1437); orderBy at 1441. The example { field: 'period_label', orderBy: 'period_start', order: 'desc' } is the spec's own fixture case. Right.
  • Duration (rules/dashboards.md Computed-column bullet, Level A table, Leave-days bullet). { name, aggregate: 'avg', duration: { from, to, unit: 'days' } } parses: DatasetMeasureDurationSchema (dataset.zod.ts 276–311) takes from / to as bare base-column names and unit ∈ seconds/minutes/hours/days; DatasetMeasureSchema's refinement (488–523) requires aggregate ∈ avg/min/max/sum and refuses field beside duration. from / to must be datetime: DATASET_DURATION_FIELD_TYPE = 'datetime' (247) — "A date is a calendar day and a time a wall clock, so neither is a point a duration is measured from" — judged at os validate by validate-dataset-measure-aggregates.ts (measure-duration-field-type-refused, 258) and at compile (400 DATASET_INVALID). So the Leave-days parenthetical "(date fields: no duration)" is true, and the escalation advice for that case is correctly left standing. Right. Nit: the Level A table row now reads "measures count/sum/avg/min/max/count_distinct, duration" — duration is a measure kind that takes one of avg/min/max/sum, not a seventh aggregate; the bullet below shows the real shape, so no author is misled, but the row reads as if aggregate: 'duration' existed.
  • Copy holes (rules/actions.md new bullet, line 22). Roots per key exactly as action-copy-template.ts declares: confirmText → record (actionConfirmTemplateRoots; a registered action's params is always the input list, which binds nothing, so record alone is right for this file's subject), successMessage → result + record (ACTION_SUCCESS_TEMPLATE_ROOTS), errorMessage → none (ACTION_ERROR_TEMPLATE_ROOTS = []). Bulk: the count hole then each param by bare name (bulkConfirmTemplateRoots, bulk-action.zod.ts 334–364; a bare tier hole, never params.tier). Refused shapes: closedScopeTemplateRefusal (flow-text-slot-template.ts 653–) refuses ${…} tokens, single-brace tokens and holes over unbound roots; the module note lists a blank sentence and a hole holding logic or an unknown formatter as door errors. Right. "errorMessage takes none" is true on BOTH surfaces (registered action: an I18nLabel, action.zod.ts 1424, shown as written; page row: a template slot with zero roots). The only hedge: on a registered action errorMessage is not a template slot at all (validate-action-copy-templates.ts ACTION_KEYS = ['confirmText','successMessage']), so a ${…} or blank there is shown raw rather than refused — the bullet's last sentence does not bind the refusals to errorMessage, so it teaches nothing false; a reader who writes a hole into errorMessage is told it "takes none" either way. The toast 'Lead … reassigned.' with a double-brace record.name hole — record is a bound root; the judge reads the root only (pathRoot), never the member, so .name is legal as written. Right.
  • onSuccess (rules/actions.md flow example + heading). The key exists on type: 'flow' (ON_SUCCESS_EXECUTORS = api / script / flow, action-on-success-executor.ts 75) and is refused on type: 'url' (action.zod.ts 2085–2090). "hops once the run completes, not when it pauses at a screen" — the executor module and flowResponse.ts 182–193 / 636–645: only the done arm completes the action; paused and refused are marked handedOff by judgeFlowLaunch and the runner performs no hop for them, "neither when the surface opens nor when the flow runner later finishes the run". The reader of ${result.output.NAME} is the console's ActionRunner.handlePostExecution → navigateOnSuccess (objectui ui/packages/core/src/actions/ActionRunner.ts ~1989, recorded by symbol in packages/spec/liveness/action.json row onSuccess.navigate); output carries the flow's isOutput variables (action.zod.ts 1713–1720, 1796). The template '/apps/crm/opportunity/record/${result.output.opportunity_id}' opens with /, an in-app path the scheme set admits. The example targets lead_conversion, a flow the skill never defines; the heading now states the variable must be isOutput, so the example is self-consistent as written. The dev's citation of examples/app-crm/src/flows/convert-lead.flow.ts:47 (opportunity_id, type: 'text', isOutput: true) is evidence for the SHAPE, not for the example's named flow — that file's flow is crm_convert_lead_wizard. Right, with the ③ note below on screen flows.

(b) The cuts — every one a restatement with a surviving home; no rule lost.

  • actions.md "author-controlled": trivial. ActionRunner.executeUrl priority sentence: an implementation note; the author-facing rule in the same paragraph ("openIn: 'self' forces in-place navigation; omit it and external/absolute URLs open in a new tab while relative URLs navigate in place") survives. // zero-roundtrip; endpoint self-auths: the prose two lines up still says "with no server round-trip … the target endpoint must enforce its own auth". "validated today, rendered once the console's print rendering ships": rules/pages.md § Print pages lines 94–95 on the head carry it verbatim in substance — "Validated today; the console's print rendering that applies it follows, so os validate warns the layout is not yet applied." Params-callout remedy → "Use one of the two mechanisms above": the section opens "There are two mechanisms here" with exactly two ### subsections, so the pointer is unambiguous. "so it matches hooks and seed data with zero relearning": the paragraph's closing sentence "Same two isolation axes, same blessed name, same undefined cases as hooks" and the code comment "identical to the hook surface" keep the hooks parity; the words "seed data" are the only thing gone — trivial.
  • dashboards.md ADR-0021 four-line widget comment: § Dataset-Bound Widgets (33–45) states "bind a dataset and select named dimensions + values", "the dataset owns the base object, allowed joins, intrinsic filter, dimensions, and certified measures", and "A widget's presentation-scope filter flows into the query as the runtime filter"; the "Guides → Analytics Datasets" pointer survives at line 294 and the report example's comment (388–389) restates the by-name binding for opportunity_metrics. All three clauses survive.
  • list-views.md 「Field type therefore decides only in the second row … Judge every entry against the object's allowed set, never the type list」: this IS a rule, and its home is the table immediately above it, which the surviving sentence calls "the whole rule" — row 1 "that list, verbatim — whatever the field types are" is the "never the type list" clause, row 2 is the one place the type list applies, and the second table below ("a real column outside the allowed set → searchable-field-unsearchable / 400 INVALID_FIELD") carries the refusal; skills/objectstack-data/SKILL.md § Search Fields adds "declare it to pin the set explicitly. Views may narrow it, never widen it." Trivially derivable; the lost words are the worked instance "a lookup in it is scanned". Acceptable under ruling A. The related-record prescription: objectstack-data § Search Fields on the head carries all three — the stored mirror field (with the project_name example), the two hooks (beforeInsert/beforeUpdate on the child, afterUpdate on the parent, as a table), and why a formula cannot be the mirror (virtual, nothing to scan, searchable-field-unsearchable); the dotted-path refusal stays in this file's own table row (account_id.name → searchable-field-unknown / 400). Nothing lost.
  • SKILL.md and references/_index.md reference none of the cut lines (read on the head; git diff --stat 756220c4 17b18cde on both is empty). Line 1 of SKILL.md untouched.

(c) Ratchet rows — pinned at the measurement; the ruling quoted where the script requires. The measure is Math.ceil(Buffer.byteLength(text,'utf8') / 4) (script line 763; no tokenizer; the SKILL.md stamp exclusion does not touch these five files). Probe over the git show blobs: rules/actions.md 8404 → 8602 bytes = 2101 → 2151; rules/dashboards.md 25001 → 24956 = 6251 → 6239; rules/list-views.md 12530 → 12515 = 3133 → 3129; evals/views-apps-actions-pages.json 6017 → 7154 = 1505 → 1789; evals/analytics-inline-vs-dataset.json 4915 → 5389 = 1229 → 1348. The five new rows are exactly these counts: zero headroom on every row, no headroom smuggled in, and the two LOWERED rows (6252 → 6239, 3141 → 3129) sit exactly at the measurement. Arithmetic: ceilings +43 −13 −12 +284 +119 = +421; bundle growth +50 −12 −4 +284 +119 = +437, the 16-token difference being the pre-existing headroom (7 + 1 + 8) absorbed — consistent with the body. The raise rule (header 127–129): "Going the other way lands only in a PR whose body quotes a maintainer ruling authorizing it" — the PR body quotes 「A:先削重复,余额按实测提高」 with the card title, the tap time and the option's own text; each raised row's comment quotes it too, the precedent rows' shape (no row-comment rule exists in the script; the self-test case 'the raise is bound to a quoted maintainer ruling' (1283) pins the REMEDY TEXT, not the rows). No lint caps line length in scripts/**; check:ratchet-remedy-authority's 400-char SHRINK-testimony window is the block ABOVE the CEILINGS doc comment, which the diff does not touch. Line counts, probe: 206 → 203, 465 → 464, 303 → 311, 65 → 85, 77 → 87; package (SKILL.md + references/**/*.md + rules/**/*.md) 2053 → 2057 lines and 28253 → 28287 tokens; whole skills/objectstack-ui/ tree 2226 → 2260 lines — every figure in the body's table reproduces. The bundle total 206107 → 206544 is the script's own sum and is not reproduced here (its delta +437 is).

(d) Evals. Both files parse (skill_name / evals[], the existing shape); ids 1–8 unique and sequential in each. views 7: must_contain ["grouping", "orderBy: 'period_start'"], must_not_contain ["orderBy: 'stage'"] — consistent with the expected output and with the refusal of a select. views 8: must_contain [the double-brace opener, "onSuccess", "result.output"], must_not_contain ["${record.name}"] — a plain JSON string; no runner in the repo reads assertions (the only must_contain readers are the two evals READMEs), so neither the double-brace opener nor ${ meets an interpreter. analytics 8: must_contain ["duration", "unit: 'days'"], must_not_contain ["beforeUpdate"] — right. The card-specific pair is present: "group opportunities by stage in pipeline order" = views 7, "Lead NAME reassigned toast" = views 8. evals/README.md describes the files generically and counts nothing; no index needs updating.

(e) Wanted lines met, nothing beyond. list-views.md: grouping + orderBy ✓ (plus the TOC entry; #grouping is a unique heading). dashboards.md:69-72: the measure named in the table row and the Computed-column bullet, with the escalation advice corrected in place for the datetime case and the Leave-days case kept with its reason ✓. actions.md:20-21: holes, roots, bulk count, params, refused shapes ✓. actions.md:64-69: onSuccess reading ${result.output.VAR} ✓. Six files, all inside the claim + amendment surface; the script diff touches only the five rows and their comments (no logic, no other row). SKILL.md line 1 untouched ✓.

(f) PR body. Line 1 is the harness attribution HTML comment, line 2 the harness _Requested by_ line, line 3 Fixes #26295, lines 4–5 Clause-②: no / Metadata-protocol: none, line-initial. os-dev.md 323 on the head: 「例外:harness 自写的 PR 正文首行归属注释原样留、只报;Fixes #N 紧随其后;dev 自写仍禁」(the rule spells the number as a placeholder) — the exemption exists; whether the _Requested by_ line is part of the harness block I cannot read, and the dev reports it as the harness's two-line block (deviation 1). No parser requires line 1: clause2-line.mjs reads the first line-initial declaration, record-recognisers deliveredCardNumber reads the body, and the three closing/claim guards on the head are success. 维护者速读: six labels in order ✓; 需要你做的 = exactly one action, Approve ✓ (Tier H); 先看 = 3 files naming the risky hunks ✓; 两分钟验证 = one command with the expected sight — the script prints ✓ check-skills-token-ratchet: PATH is N tokens (ceiling C; headroom H). per file (line 926), so "headroom 都是 0" is what it prints ✓. English sections What changes / Tests / Acceptance notes / Verification with readings folded into details blocks ✓. Both readings (whole-file and whole-package before/after, lines and tokens) present and reproduced above ✓. Blemish: the Verification line reads "on head {HEAD}" — an unfilled placeholder (cosmetic; the head is named in the report and on the PR).

(g) Report. os-dev-report 6431647940 at 2026-10-27T01:43Z: deviations (7), line_budget, out_of_scope_findings (3) present as the order asked; files_changed / rules_retired absent — and NOT in the os-dev.md JSON template on the head (its keys: issue, status, branch, pr, session, premise_still_valid, summary, tests, gates, dogfood_battery, mcp_calls, api_writes, open_questions, out_of_scope_findings), so nothing is missing. Branch claude/project-thread-5txv5f = the claim's branch ✓. mcp_calls: 0, api_writes: 2 (relay pr_create, the report comment) ✓. The PR's assignee os-warren and label skip-changeset were written by the relay at pr_create (the PR is authored by objectstack-fleet[bot]; documentation and size/m are the labeler's). Commit trailers: the model-free pair; no model name in the commit or the PR body.

② Semver level

skip-changeset is right. skills/objectstack-ui/ carries no package.json (its 11 files on the head: SKILL.md, evals/*, references/*, rules/*), so no npm package's files[] moves; scripts/check-skills-token-ratchet.mjs is repo tooling. Check Changeset on the head: success. Clause-②: no and Metadata-protocol: none are line-initial on the PR body (lines 4–5) and match the claim.

③ Boundary flags

  1. Base reconciled. The branch forks from 756220c4 (the body's "base 756220c"); the PR's recorded base e931621a is main's tip at creation (one commit later, ci(sdui)); origin/main is now 2163e806 (seedFile). git diff --stat 756220c4 origin/main over the scope paths touches only .github/workflows/lint.yml (a comment block) and skills/objectstack-data/references/_index.md (generated, +1): no file of this PR moved on main; no rebase is owed.
  2. The dev's note 1 (orders price lines, the gate prices tokens): a 收班简报 note for the seat, already carried by ruling A for all eight audit cards — not a card.
  3. The dev's note 2 (errorMessage is an I18nLabel on a registered action, a template slot on a page row): verified by reading (action.zod.ts 1424; validate-action-copy-templates.ts 54–58). The teaching is true on both surfaces; nothing to file.
  4. The dev's note 3 (.github/PULL_REQUEST_TEMPLATE.md places the two declaration lines under ## Verification, os-dev.md puts them line-initial after Fixes): verified — the template (756220c) has Fixes #, What changes / Why / Verification, then the two lines, then a Human Minimum checklist; both parsers read line-initial lines anywhere, so one placement satisfies both. A 收班简报 note (the two texts could name one order); not a card.
  5. Screen flows and the onSuccess example — a note for the seat's 席位核对, not a defect in the spec reading: the executor module and judgeFlowLaunch say a run that pauses at its first screen is handed off and the runner never hops for it, so onSuccess beside a type: 'flow' action is live only for a flow whose run COMPLETES at launch (an autolaunched flow, or one that reaches end without a screen). The example's old parenthetical "(delegates to a screen flow)" was replaced by the new heading, which states the rule correctly; examples/app-crm's convert flow is a type: 'screen' wizard whose opportunity_id is bound by a screen step, so an author who pairs this example with THAT flow gets an inert hop. No lint judges onSuccess against the target flow's type (0 hits in packages/lint/src). One word in the heading ("a flow that completes, e.g. autolaunched") would close it; optional, costs tokens on a row now at zero headroom.
  6. SKILL.md line 146 lists the list-views topics ("filtering, userFilters, toolbar search, sorting, kanban, gantt") without "grouping" — optional; SKILL.md is a ratchet row of its own and the order's surface excluded its line 1 only.
  7. Lane mapping in the brief was stale: the ratchet, its self-test, check:ratchet-remedy-authority, check:skill-docs and check:skill-refs run in Type Check · source gates; check:skill-examples in Type Check · consumer gates (lint.yml on origin/main). Both success on the head, so the verdicts stand.

Implemented-by: claude/project-thread-5txv5f
Reviewed-by: session_01LqKsGyMNFwwnbF5FTAqyPn

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读

改了什么:objectstack-ui 技能包补上 18.2 的四条教学——列表 grouping.fields[] 与 orderBy、数据集 duration 度量、动作文案的 {{ }} 占位(各键的根、bulk 的 count、四种拒收形态)、flow 动作的 onSuccess 读 ${result.output.NAME};配三条 eval。按你的裁决 A:先在同文件删重复(三处共 −300 token,去处都点了名),余额按实测改棘轮里该包五行的天花板(两行反而降了)。
为什么改:AI 作者在 18.2 上按阶段分组列表、算平均成交时长、写带记录名的 toast、流程跑完后跳到新建记录,技能一条都没教(审计 #26184 的 U1/U2/U4/U5 行);${record.name} 写进 toast 会在 os validate 被拒。
风险与回滚:只改技能 markdown、eval JSON 和棘轮五行数字;不发版、无运行时改动;回滚 = revert 本 PR。风险在教学措辞是否属实——复核员逐句对着 spec 源码核过(见下),三条新示例也经 zod 解析探针。
先看:① skills/objectstack-ui/rules/actions.md 文案占位一行与 flow 示例的 onSuccess;② rules/list-views.md 新 Grouping 节(select 按选项顺序、orderBy 只接数字/日期/布尔);③ scripts/check-skills-token-ratchet.mjs 五行(+43 / −13 / −12 / +284 / +119,每行钉在实测值上)。
两分钟验证:node scripts/check-skills-token-ratchet.mjs 预期全绿,objectstack-ui 的 actions / dashboards / list-views 与两个 eval 文件各印 headroom 0。
需要你做的:Approve(Tier H,skills/** 是发布面);席位随后落地。
席位意见:同意落地。两处可选的后续(不挡本 PR):flow 示例的 onSuccess 只在运行一次跑完的 flow 上生效,屏幕流目标会是一次空跳,标题加一个词即可;SKILL.md L146 列表视图的主题列表还没写 grouping。

席位核对

ACCEPT · domain:skills#2 · session_01LqKsGyMNFwwnbF5FTAqyPn · 2026-10-27T01:59Z — seat review of record for #26295 on head 17b18cde, against GitHub, not the report's own account. Published surface skills/** ⇒ Tier H (check-governed-merges --pr 26385: skills/objectstack-ui/** ×5 on the register, scripts/check-skills-token-ratchet.mjs off it, 137 changed lines): the record of this tier is the independent ## Contract review at comment 6431836359 (Served-tier: CONTRACT_REVIEW_TIER, head 17b18cde, VERDICT: PASS, one declared probe — wc -c over git show blobs to recompute the ratchet's measure; drafted by a reviewer other than the implementer, the card thread's dev). This seat adds the landing read; the landing itself waits for a GOVERNED_APPROVERS approval — ⛔ the seat does not ready, arm or enqueue a Tier H PR.

  1. Shape. Draft, base main, mergeable: true (clean), one commit; body lines 1–2 the harness attribution block, line 3 Fixes #26295 (os-dev.md's harness exemption; no parser requires line 1; the three claim/closing guards green), Clause-②: no, Metadata-protocol: none line-initial; ## 维护者速读 six labels in order, then What changes / Tests / Acceptance notes / Verification with folds. Files: rules/actions.md (+10/−13), rules/dashboards.md (+7/−8), rules/list-views.md (+19/−11), evals/views-apps-actions-pages.json (+20), evals/analytics-inline-vs-dataset.json (+10), scripts/check-skills-token-ratchet.mjs (+34/−5, five rows) — the claim's surface as amended at 2026-10-27T01:26Z (claim 6430927788 + the CEILINGS rows under ruling A); line 1 of SKILL.md untouched. Labels documentation, size/m, skip-changeset — right, no package.json under skills/objectstack-ui. Branch claude/project-thread-5txv5f, named by the claim. Reviewers hotlong + os-zhuang requested by the relay at pr_create.
  2. Record, measured. The reviewer read every new sentence against the spec on origin/main: GroupingField field/orderBy/order (view.zod.ts 1422–1455), list-view-group-order-unorderable from checkGroupOrder over the min/max compatibility row (numeric, temporal, boolean; text/select refused), select option order in the grid's compareGroupEntries/groupOptionRanks; duration from/to are datetime only (DATASET_DURATION_FIELD_TYPE), so 「date fields: no duration」 is true; copy roots exactly per action-copy-template.ts / bulk-action.zod.ts; onSuccess on type: 'flow', refused on url (action.zod.ts 2085), completed-run-only (judgeFlowLaunch), ${result.output.NAME} read by objectui ActionRunner.handlePostExecution. Every cut has a surviving home (pages.md 94–95; objectstack-data § Search Fields carries mirror + two hooks + formula refusal; Dataset-Bound Widgets carries the three ADR-0021 clauses; the allowed-set paragraph is the table above it restated). Five ratchet rows pinned exactly at the recomputed measurement (2151 / 6239 / 3129 / 1789 / 1348, zero headroom; the two lowered rows at measurement); +421 ceilings / +437 bundle reproduces; ruling A quoted in the rows and the body. Evals 7, 8 and analytics 8: ids sequential, assertions consistent, both card-specific evals present. The seat re-read the diff and reads the same.
  3. Gates, pinned to head 17b18cde. 50 check-runs, 34 success and 16 skipped, none red or pending (reviewer's read 2026-10-27T01:52Z; the seat read mergeable_state: clean after). Token ratchet + self-test, check:skill-docs, check:skill-refs ride Type Check · source gates; check:skill-examples rides Type Check · consumer gates — both success; Lint & Repo Gates, Test Core, TypeScript Type Check, Lint · ESLint, Check Changeset, the claim/closing guards all success.
  4. Accepted deviations. (a) Surface widened to the five CEILINGS rows — the seat's own amendment under ruling A. (b) The order's "pipeline order via orderBy" premise corrected by the dev: a select level already keeps its option order and orderBy refuses a select; taught as such. (c) Cuts beyond 「the stale lines this card names」 — ruling A's duplicate cuts, each with a named home (see 2). (d) Nine cited commits unreachable in the shallow clone; spec lines cited instead. (e) The body's Verification line carries an unfilled {HEAD} placeholder — cosmetic, left as is (a body PATCH is a write); the head is 17b18cde.
  5. Carrier. Nothing to file. 收班简报: the audit dispatch orders price lines while the binding gate for skills/** is the token ratchet (ruling A covers all eight); .github/PULL_REQUEST_TEMPLATE.md (756220c) places the two declaration lines under Verification while os-dev.md puts them line-initial after Fixes — both parsers accept either, one order should be named; optional follow-ups in the 席位意见 line above. The review brief's lane mapping for skills gates corrected for the next reviews.
  6. Landing. Tier H ⇒ this record and the contract review are on the head; the PR waits for a GOVERNED_APPROVERS approval (the relay's summons at create went to hotlong + os-zhuang). On that approval the seat readies it and arms auto-merge through the relay, follows it to MERGED, and closes skills(objectstack-ui): teach list-view grouping and orderBy, the duration measure, action copy holes, and a flow action's onSuccess #26295 with the landing record.

Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review October 27, 2026 02:38
@os-zhuang
os-zhuang enabled auto-merge October 27, 2026 02:38
@os-zhuang
os-zhuang added this pull request to the merge queue Oct 27, 2026
Merged via the queue into main with commit 591a796 Oct 27, 2026
51 checks passed
@os-zhuang
os-zhuang deleted the claude/project-thread-5txv5f branch October 27, 2026 03:03
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

Development

Successfully merging this pull request may close these issues.

skills(objectstack-ui): teach list-view grouping and orderBy, the duration measure, action copy holes, and a flow action's onSuccess

3 participants