Repository navigation
skills(objectstack-ui): teach list-view grouping and orderBy, the duration measure, action copy holes, and a flow action's onSuccess - #26385
Conversation
…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>
Contract reviewReviewed at 2026-10-27T01:57Z — PR #26385 (card #26295, Served-tier: Check-runs on ① Derived judgments(a) Every new sentence against the spec on
(b) The cuts — every one a restatement with a surviving home; no rule lost.
(c) Ratchet rows — pinned at the measurement; the ruling quoted where the script requires. The measure is (d) Evals. Both files parse ( (e) Wanted lines met, nothing beyond. (f) PR body. Line 1 is the harness attribution HTML comment, line 2 the harness (g) Report. os-dev-report 6431647940 at 2026-10-27T01:43Z: ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
维护者速读改了什么:objectstack-ui 技能包补上 18.2 的四条教学——列表 席位核对ACCEPT ·
Generated by Claude Code |
Requested by Warren · project thread
Fixes #26295
Clause-②: no
Metadata-protocol: none
维护者速读
grouping.fields[]与orderBy、数据集duration度量、动作文案的{{ }}占位(各键的根、bulk 的count、四种拒收形态)、flow 动作的onSuccess读${result.output.NAME};配三条 eval。新增先在同文件删重复内容付账,余额按裁决 A 提高棘轮里该包五行的天花板。${record.name}写进 toast 会在os validate被拒。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示例编译通过。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; aselectlevel keeps its declared option order, so "stage in pipeline order" is{ field: 'stage' }with the stages declared in that order;orderByplaces 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— thedurationmeasure, in place: the Level A table's measures row names it, the Computed column bullet routes "time between twodatetimefields 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 (datefields, so noduration). No parallel section.rules/actions.md— one bullet on the copy slots: holes are{{ }},confirmTextreadsrecord,successMessagereadsrecordandresult,errorMessagefills none, abulkActionDefsentry'sconfirmTextreads{{ count }}and its params by bare name, and${record.name},{name}, logic in a hole and a blank sentence failos validate. The reassign example's toast now carries a hole ('Lead {{ record.name }} reassigned.'). The flow example gainsonSuccess: { 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 (anisOutputvariable).evals/views-apps-actions-pages.json— eval 7 (group opportunities by stage in pipeline order, forecasts byperiod_labelnewest first) and eval 8 ("Lead NAME reassigned" toast + open the opportunity a flow created).evals/analytics-inline-vs-dataset.json— eval 8 (average days fromcreated_attoclosed_at). Same shape as the files' existing entries.scripts/check-skills-token-ratchet.mjs— the fiveCEILINGSrows of the files above, moved to the measured counts (the ruling and arithmetic below).Line readings (whole file, before → after, on base 756220c):
ceil(utf8 bytes / 4), the sibling gate's unit)rules/actions.mdrules/dashboards.mdrules/list-views.mdSKILL.md+references/**/*.md+rules/**/*.md)evals/views-apps-actions-pages.jsonevals/analytics-inline-vs-dataset.jsonskills/objectstack-ui/treePaid down first, then raised — the token ratchet.
node scripts/check-skills-token-ratchet.mjsprices 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 thatActionRunner.executeUrlreadsopenInwith 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 theSalesDashboardexample restating that widgets bind a dataset and select by name and thatfilteris 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 aformulacannot 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 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
groupingwas 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 atos validate); or take the user to the record a flow created (onSuccessis read fortype: 'flow'since the executor set widened, and the flow example lacked it).Premise checks against
origin/main756220c (the order's hypotheses; the cited commits are unreachable in this shallow clone too —git cat-file -tanswers missing for all nine — so the spec lines are the evidence):grouping.fields[].orderByispackages/spec/src/ui/view.zod.ts:1441. But aselectlevel already groups in its declared option order — the grid's group comparator ranks option values by the field'soptions(ui/packages/plugin-grid/src/useGroupedData.ts,compareGroupEntries;ObjectGrid.tsxbuildsgroupOptionRanks), andorderByover a select is refused bylist-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 byAGGREGATE_FIELD_TYPE_COMPATIBILITY). So the pipeline-order case is taught as{ field: 'stage' }with noorderBy, and theorderByexample is the spec's own fixture case,period_labelordered byperiod_start(view-grouping-query.test.ts:595). Eval 7 expects exactly that split.DatasetMeasureDurationSchemaatpackages/spec/src/ui/dataset.zod.ts:276(from,to,unit∈ seconds/minutes/hours/days, aggregates avg/min/max/sum,datetimecolumns 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 aredate.packages/spec/src/ui/action-copy-template.ts(actionConfirmTemplateRoots,ACTION_SUCCESS_TEMPLATE_ROOTS,ACTION_ERROR_TEMPLATE_ROOTS); bulk rootscount+ params by bare name perbulk-action.zod.ts:334–364. One nuance kept out of the teaching: a registered action'serrorMessageis anI18nLabel, not a template slot (action.zod.ts:1424), andvalidate-action-copy-templates.tsjudgeserrorMessageonly on page action rows — "fills none" is true on both surfaces.onSuccessbesidetype: 'flow'(action-on-success-executor.ts:25–33;action.zod.ts:1719–1721),${result.output.VAR}reading the flow'sisOutputvariables; the example'sopportunity_idis declaredisOutput: truebyexamples/app-crm/src/flows/convert-lead.flow.ts:47.orderBy/duration/onSuccessin the package's evals on base; all three evals are new.Tests
No TypeScript changes; the gate that compiles the package's
os:checkexamples and a zod parse probe of every new shape are the tests.tsx, againstpackages/spec/srcon this head; script in the dev's scratchpad, nothing committed):ActionSchema.safeParseof the reassign example (successMessage: 'Lead {{ record.name }} reassigned.') → PARSES; of the convert example withonSuccess.navigate→ PARSES; control,onSuccesson atype: 'url'action → REFUSED ("aurlaction is itself the navigation…").closedScopeTemplateRefusalover thesuccessMessageroots:'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'withduration→ REFUSED ("a duration measure takesaggregateavg / min / max / sum");fieldbesideduration→ REFUSED ("it takes nofield").GroupingFieldSchema.safeParse({ field: 'stage' })→ PARSES;{ field: 'period_label', orderBy: 'period_start', order: 'desc' }→ PARSES; control, paddedorderBy: ' period_start'→ REFUSED (the non-padded rule).json.load(shape unchanged:id/prompt/expected_output/files/assertions).pnpm exec eslint --no-inline-config scripts/check-skills-token-ratchet.mjs→ exit 0.Acceptance notes
check:skill-docs,check:skill-refs,check:skill-examplesgreen; 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 fiveCEILINGSrows inscripts/check-skills-token-ratchet.mjs, outsideskills/objectstack-ui/**. The seat amends the claim's file surface for them.actions.md:20-21,:64-69;dashboards.md:69-72,:90-92) are rewritten in place, not dropped.references/_index.mdis generator-owned and unchanged (check:skill-refsgreen); line 1 ofSKILL.mdis untouched (PR chore: version packages #25640 owns it).rules/actions.mdstill sayserrorMessageis "toast copy" for a registered action, which is right; that a page action row'serrorMessageis additionally judged as a template slot is an objectui-surface nuance with no author-facing choice.evals/README.mddescribes the two files generically; no edit needed.skip-changeset: the skills packages carry nopackage.jsonand nothing in a released package'sfiles[]moves; the ratchet script is repo tooling.Verification
Gates derived with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon 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 0pnpm --filter @objectstack/spec check:skill-refs→ exit 0 (references/_index.mdin 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 editedos:checkblocks inrules/actions.mdamong them), after building@objectstack/client-react/@objectstack/clientunder the locknode 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 206544Every other derived family, by exit code
node scripts/check-ci-filter-parity.mjs→ exit 0node scripts/check-closing-keyword-parity.mjs→ exit 0node scripts/check-closing-keyword-parity.mjs --self-test→ exit 0node scripts/check-comment-mask-corpus.mjs→ exit 0node scripts/check-declaration-mirrors.mjs→ exit 0node scripts/check-declaration-mirrors.mjs --self-test→ exit 0node scripts/check-doc-route-spelling.mjs --advisory→ exit 0node scripts/check-doc-route-spelling.mjs --self-test→ exit 0node scripts/check-dts-emitted.mjs --self-test→ exit 0node scripts/check-scripts-symbol-anchors.mjs→ exit 0node scripts/check-scripts-symbol-anchors.mjs --self-test→ exit 0node scripts/check-self-test-wired.mjs→ exit 0node scripts/check-self-test-wired.mjs --self-test→ exit 0node scripts/check-self-test-workflow-commands.mjs→ exit 0node scripts/check-self-test-workflow-commands.mjs --self-test→ exit 0node scripts/check-whole-set-label-write.mjs→ exit 0node scripts/check-whole-set-label-write.mjs --self-test→ exit 0node scripts/ci/assert-turbo-remote-cache.mjs --self-test→ exit 0node scripts/ci/prune-turbo-cache.mjs --self-test→ exit 0node scripts/pm/bare-root-worklist.mjs --self-test→ exit 0pnpm --filter @objectstack/lint run check:doc-formula-expressions→ exit 0pnpm check:agent-test-spelling→ exit 0pnpm check:bash32-floor→ exit 0pnpm check:cli-command-ids→ exit 0pnpm check:corpus-claim-drift→ exit 0pnpm check:cross-package-test-inputs→ exit 0pnpm check:doc-authoring→ exit 0pnpm check:driver-memory-census→ exit 0pnpm check:dts-closure→ exit 0pnpm check:entry-guard→ exit 0pnpm check:gitlink-declared→ exit 0pnpm check:nul-bytes→ exit 0pnpm check:parse-guard→ exit 0pnpm check:pm-dispatch-gates→ exit 0pnpm check:pm-governed-merges→ exit 0pnpm check:pm-skill-id-lint→ exit 0pnpm check:pnpm-filter-targets→ exit 0pnpm check:pnpm-flag-spellings→ exit 0pnpm check:ratchet-remedy-authority→ exit 0pnpm check:refd-timer-probe→ exit 0pnpm check:role-word→ exit 0pnpm check:skill-compatibility→ exit 0pnpm check:skill-frame-sync→ exit 0pnpm check:skill-identifier-liveness→ exit 0pnpm check:sourcemap-no-sources-content→ exit 0pnpm check:watch-hint-literal→ exit 0NOT 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 builtdist/(93 packages); CI'sLint & Repo Gatesbuilds the tree firstpnpm check:lean-entry-closure→ exit 3 — loads built entry points (@objectstack/objectql/coreabsent here); same CI familyReconciliation, 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).scripts/pm/os-verify-lock.sh(slotissue-26295):pnpm --filter @objectstack/spec buildVERDICT command-exit 0 (64 s held);turbo run build --filter=@objectstack/formula --filter=@objectstack/lintexit 0;turbo run build --filter=@objectstack/client-react --filter=@objectstack/clientVERDICT command-exit 0 (133 s held).pnpm check:pm-dispatch-gates→ exit 0 (fast tier, 1901 cases), run detached as os-dev directs.test/typecheck(no TypeScript changed;dispatch-gatesderives no type-check program for this diff);test:repo,pnpm lintand the whole-tree scans are CI's. Dogfood battery: n/a — no!in the title and no(narrowing)arm.Generated by Claude Code