Skip to content

docs(skills): correct 25 false facts in the published objectui skill package - #7098

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-7094-skills-sweep-objectui
Sep 1, 2026
Merged

docs(skills): correct 25 false facts in the published objectui skill package#7098
os-zhuang merged 1 commit into
mainfrom
claude/issue-7094-skills-sweep-objectui

Conversation

@os-sam

@os-sam os-sam commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Fixes #7094
Part of objectstack-ai/objectstack#13658

Flight ⑫ of the published-skills factual sweep — skills/objectui/**, the program's last package. 18 files, 5,729 markdown lines at origin/main@2c3cd1b.

Session, for durable attribution: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2

⛔ Governed surface — human merge, deliberately parked

skills/** is the published, customer-facing surface. This PR is a draft on purpose: not marked ready, auto-merge not armed, not enqueued, and the dispatching seat will not arm it. The repo's own guard agrees — node scripts/check-governed-queue-guard.mjs --test over this diff answers:

One governed path governs the WHOLE pull request — proportion is not a question.
⛔ Do not flip it ready, enqueue it, or arm auto-merge. Park it as a DRAFT and leave the merge
   to the maintainer; a human merge IS the review record for a governed surface.

Method

Per behavioural claim: locate the implementing code, verify there — never against another document — and execute it where the claim is behaviour-bearing. Verdicts are VERIFIED / FALSE / NOT MEASURABLE, none silently skipped. Split by file (the package-size allowance), reported in the dev report on #7094.

Two mechanical sweeps ran over the whole package rather than by eye, both reproducible from the base tree: every backticked API identifier and every @object-ui/* import binding inside a fenced block, resolved against the real export surface of the package each names; and every in-repo path assertion. Deduplicated, that is 310 distinct (file, assertion) pairs. Both sweeps are what surfaced the phantom-export class below — neither would have been caught by reading, because in every case the surrounding prose is coherent and only the name is dead.

False density: 25 false claims / 340 adjudicated — 7.4%, inside the program's 1.5–10% working range and at the top of it. The denominator is the 310 mechanical assertions plus the 30 enumerations probed against their schema's member set. A handful of the numerator (the coverage thresholds, the stale line range, the sibling count) are value claims that sit outside that mechanical substrate, so 7.4% is a ceiling on the density, not a point estimate.

The distribution is lopsided and worth naming: three guides carry 18 of the 25. guides/mobile.md alone documents an @object-ui/mobile that does not exist — seven phantom exports across three consecutive code blocks — and the app-shell sections of three more guides teach a *Renderer naming scheme the package never had.

The corrections — 25 false facts, 34 landing sites

Class 1 — phantom exports (10 facts, 13 sites)

# 落点 before after
F1 guides/mobile.md — gesture block import { useSwipe, useLongPress, usePinchZoom } from '@object-ui/mobile' useGesture (one hook, type + onGesture, returns a ref), usePullToRefresh; GestureType's ten values named
F2 guides/mobile.md — overlay block import { BottomSheet, MobileNav } ResponsiveContainer (minBreakpoint/maxBreakpoint/showOn/hideOn/fallback) + the real overlays from @object-ui/components; mobile nav is app-shell's mobileNavMode
F3 guides/mobile.md — inputs block import { MobileSelect, MobileDatePicker } no mobile widget set exists; @object-ui/fields + useTouchTarget
F4 guides/mobile.md — offline block const { isOnline, queue, syncState } = useOffline() pendingCountOfflineResult has no queue
F5 guides/mobile.md — sync states idle → syncing → synced | error 'idle' | 'syncing' | 'error' | 'offline' — there is no 'synced'
F6 guides/page-builder.md, guides/project-setup.md, guides/architecture.md (3 sites) ObjectRenderer, PageRenderer, DashboardRenderer from @object-ui/app-shell ObjectView, RecordDetailView, PageView, DashboardView, ReportView, with a note on where the two real *Renderer names actually live
F7 guides/architecture.md Pattern A // packages/core/src/registry.ts + registerComponent() / resolveComponent() packages/core/src/registry/Registry.ts, the ComponentRegistry instance, register(type, component, meta?) / get(type, namespace?)
F8 rules/composition.md Empty taking icon / title / description props the Empty / EmptyMedia / EmptyTitle / EmptyDescription composition family
F9 rules/composition.md (2 sites) the data-icon="inline-start" rule, and Spinner carrying that attribute icons are children; buttonVariants' base already carries gap-2 [&_svg]:size-4 [&_svg]:shrink-0
F10 rules/composition.md Button taking an icon prop (icon={CheckIcon}) ButtonProps has no icon prop — icon is the square-button size variant

data-icon occurs zero times in packages/, apps/, examples/, e2e/, scripts/, docs/ and content/. ObjectRenderer occurs six times repo-wide, all six in one example README — filed as #7095.

Class 2 — an enumeration that stopped growing when the schema did (11 facts, 16 sites)

# 落点 before after
F11 guides/schema-expressions.md — Formula functions 16 named 30 — added MEDIAN PERCENTILE STDEV VARIANCE SWITCH TRIM LEN LEFT RIGHT SUBSTRING FIND REPLACE REGEX DATEDIFF, with the two non-obvious argument orders spelled out
F12 guides/schema-expressions.md — "Supported operators (full list)" no typeof, no primaries unary row (!, -, +, typeof) + primaries row (array literals, single-param arrow)
F13 guides/schema-expressions.md — Security model blocked list presented as the whole gate names isDangerous as the source-level table and adds the four BLOCKED_PROPS members it omits
F14 guides/data-integration.mdDataSource saveView? and execute? shown as members neither exists — replaced with the real createView/updateView/updateViewConfig/deleteView; the optional half is 32 members, not 6
F15 guides/data-integration.mdQueryParams 6 $ keys 9 — added $search, $searchFields, $count
F16 guides/plugin-development.md — "ComponentMeta options (full reference)" 10 rows 16 — added tier, labelling, deprecated, examples, tags, description
F17 guides/plugin-development.mdComponentInput type a single literal; 8 keys ComponentInputControlType | ComponentInputControlType[] (objectui#3832); 13 keys — added inputType, min, max, step, placeholder
F18 guides/plugin-development.mdFieldWidgetComponentProps "and nothing else" 5 plumbing keys adds dependsOnLabels, onUploadingChange, onSelectRecord, onCreateNew, and names the real intersection (FieldWidgetDomProps & AriaAttributes & data-${string})
F19 guides/app-composition.md — nav target table 7 of 9 union members all nine, with rows for component (componentRef, colon-joined), separator and action
F20 README.md, guides/architecture.md, guides/page-builder.md, guides/project-setup.md (4 sites) plugin catalogue lists plugin-workflow, omits plugin-tree @object-ui/plugin-workflow does not exist; @object-ui/plugin-tree does, registering tree and object-tree. Count of 19 was right; one member was wrong
F21 guides/architecture.md — CLI list omits serve serve added (.command('serve'); dev is described by the CLI as its alias)

Class 3 — accept surface vs read surface (1 fact, 3 sites)

# 落点 before after
F22 SKILL.md §6, rules/protocol.md, guides/mobile.md columns takes a breakpoint object keyed xs/sm/md/lg/xl the spec's BreakpointColumnMapSchema accepts six keys including 2xl; the grid renderer reads only five, so a 2xl entry parses and is then silently dropped

Measured through a real SchemaRenderer render, reading the emitted class: {xs:1, xl:5}grid grid-cols-1 xl:grid-cols-5 gap-4; {xs:1, "2xl":6}grid grid-cols-1 gap-4. The implementation half is filed as #7097 with both routes and the gate that generalizes.

Class 4 — stale coordinates and self-description (3 facts, 6 sites)

# 落点 before after
F23 guides/testing.md (3 sites) "Two test suites in vitest.workspace.ts", split by package, setup vitest.setup.tsx no such file — three projects (unit/dom/dom-heavy) declared in vitest.config.mts, split by file extension, each with its own setup file; vitest.setup.tsx is a legacy shim wired into no config
F24 guides/testing.md coverage 62 / 54 / 50 / 61 40 / 33 / 30 / 40, the real coverage.thresholds
F25 guides/schema-expressions.md, guides/project-setup.md, README.md SchemaRenderer.tsx "(lines 117-175)" (that range is resolveAriaProps); "its ten siblings" (there are 17); layout tree listing 10 of 12 guides the evaluatedSchema memo by name; "sixteen siblings"; architecture.md + app-composition.md restored

Two test patterns in guides/testing.md were rewritten because, as published, they cannot pass: new ExpressionEvaluator() then evaluate(expr, context) puts the context where EvaluationOptions goes, so every ${…} resolves against an empty scope and the template part falls back to its own literal (measured — 'Hello ${data.name}' comes back verbatim); and formatValidationErrors(result.errors) passes the errors array to a function whose parameter is the whole result. Both are counted inside F23–F25's file rows.

Executed evidence

Three probe files, written and run against the real modules, then deleted — this PR adds no test files.

unit project, 13/13 pass. The claims proven TRUE by execution (the non-vacuity control): the six safe globals resolve and evaluate; single-${} type preservation returns 42 / true and "Count: 42" for the mixed template; ${0 && "yes"} returns number 0; a missing variable returns undefined without throwing. And all 13 patterns in the published "Blocked" list really throw Potentially dangerous expression detected: ….

The FALSE ones, measured:

${new Date(data.timestamp)} => RETURNED a real Date: 1970-01-01T00:00:00.000Z
new Function()              => TypeError: new Function() is not supported in expressions
formula registry: registered=30 documented=16
  UNDOCUMENTED: DATEDIFF FIND LEFT LEN MEDIAN PERCENTILE REGEX REPLACE RIGHT
                STDEV SUBSTRING SWITCH TRIM VARIANCE
  PHANTOM (documented but not registered): (none)
evaluate(expr, context)     => "Hello ${data.name}"   (the source string, not "Hello Alice")

Every rewritten line re-executed: the 14 new formula calls with their real argument order (FIND("@", s)3, PERCENTILE(90, values)8.6, SWITCH(…)"silver", TRIM"hi", DATEDIFF7), and both rewritten test patterns pass 5/5 and 3/3.

dom project, 8/8 + 3/3 pass. Export surfaces asserted against the real modules: the seven-part Empty family and Spinner present; buttonVariants() contains gap-2 and [&_svg]:size-4; the ten real @object-ui/mobile exports present and all seven phantoms absent; BREAKPOINTS equals {xs:0, sm:640, md:768, lg:1024, xl:1280, '2xl':1536}; the thirteen real app-shell names present and all three *Renderer names absent; PageRenderer absent from @object-ui/components' public surface and DashboardRenderer present on @object-ui/plugin-dashboard. Plus the 2xl grid measurements above.

Budget — both readings, per the 2026-08-21 ruling

reading before after delta
skills/objectui/** package lines (all 18 .md) 5,729 5,810 +81
package bytes 223,325 231,783 +8,458 (+3.8%)
package tokens (ceil(utf8 bytes / 4)) 55,832 57,946 +2,114
guides/schema-expressions.md 628 654 +26
guides/plugin-development.md 426 446 +20
guides/testing.md 383 398 +15
rules/composition.md 297 277 −20
guides/architecture.md 192 189 −3

No token or line ratchet is in force in objectui — absence measured, not assumed. The only skills/**-scoped gate is scripts/check-skills-paths.mjs, whose "ratchet" is its baseline exemption list, not a size budget; no script or workflow in the repo carries a skills token or line ceiling. So byte-neutral-or-shrinking does not bind here — which is stated so a reviewer can hold the growth to the ruling rather than to a gate.

The +3.8% is disclosed for that judgement. It is dominated by enumerations that were incomplete: 14 formula-function names, 6 ComponentMeta options, 5 ComponentInput keys, 3 QueryParams keys, 3 nav-union rows. Every added line carries a fact that was missing or wrong; nuance that did not correct a falsehood was cut rather than kept (the security-model paragraph was trimmed from 8 lines to 4 on that rule), and two files shrink.

Gates — derived from objectui's own tooling, run at head a3dc965

scripts/pm/dispatch-gates.mjs lives only in objectstack and answers only about that tree, so the family was derived here from this repo's own package.json scripts and .github/workflows/** — every check-*.mjs whose scan surface names skills/, plus the unfiltered per-PR gates.

EXIT=0 :: node scripts/check-skills-paths.mjs
          ✅ 92/93 stated path(s) resolve across 18 guide file(s); 1 baselined
EXIT=0 :: pnpm check:shell-escape-residue      (skills: 18 files, 233 fences)
EXIT=0 :: pnpm check:control-bytes             (5,883 tracked text files)
EXIT=0 :: node scripts/check-changeset-presence.mjs
EXIT=0 :: node scripts/check-governed-queue-guard.mjs --self-test   (132 cases)
EXIT=0 :: pnpm check:doc-fences                (224 documents)
EXIT=0 :: pnpm docs:check-links                (17 scan roots)

Every exit code was captured before any pipe. Two gates are recorded honestly as NOT MEASURED rather than as passes:

  • node scripts/check-governed-queue-guard.mjs (no flag) exits 1 with "could not read GITHUB_EVENT_PATH" — by its own text it reads the workflow event payload and nothing else, so it is a PREREQUISITE NOT MET locally. Its --self-test half is green above and the real run happens in CI.
  • pnpm check:readme-exports exits 1 with 386 findings, every one of them "its type entry ./dist/index.d.ts is not on disk — run pnpm build first". Its population is the 43 READMEs under packages/ (its own census line: "0 outside any package"); zero of its findings touch skills/, and this diff changes none of its inputs.

ESLint was not run repo-wide, and that narrowing is measured, not skipped. Its population, read from eslint.config.js itself, is **/*.{ts,tsx} (plus **/*.tsx and two test globs) — there is no markdown arm. npx eslint skills --format json reports 0 files linted, 0 messages. And the config declares no projectService / project, so there is no cross-file type program through which a .md-only diff could move an untouched file's verdict in either direction.

Changeset — none owed, and no label either

The repo's own gate decided it from the diff: "13 file(s) changed, 0 of them published source of a package the release covers … ✅ No source or published contract of a released package changed in this range, so no changeset is owed."

No skip-changeset label is applied, deliberately. In this repo that label is a phantom: scripts/__tests__/ci-cd-pipeline-doc.test.ts fails if any file under .github/ or scripts/ so much as mentions it (objectui#4912). Nothing reads it, so applying it would be theatre.

Filed, not fixed — out of scope by file surface

All three are unassigned, deduped against the 274 open issues in this repo before filing, and link back to #7094.

What is deliberately not here

  • No gate binding this prose to behaviour. The sharpest candidate this flight found — comparing a presented-as-exhaustive enumeration against the schema member set it mirrors — is a new validation surface and belongs on its own card, not smuggled into a docs PR.
  • guides/console-development.md, guides/auth-permissions.md, guides/i18n.md, rules/styling.md and rules/no-touch-zones.md are unchanged: every claim adjudicated in them came back VERIFIED. console-development.md's retired-names table is right in both directions — all 16 symbols it declares nonexistent return zero hits repo-wide.
  • SKILL.md carries one added clause (F22) and nothing else.

Generated by Claude Code


Generated by Claude Code

…package

Flight 12 of the published-skills factual sweep (objectstack#13658). Every
claim in `skills/objectui/**` that names an operator, a key, an export or an
asserted output was located in the IMPLEMENTING CODE and verified there --
never against another document -- and executed where behaviour-bearing.

Dominant class, as the program predicted: an enumeration that stopped growing
when the schema did. 30 formula functions documented as 16; DataSource's 32
optional members shown as 6 (two of them spellings that do not exist);
ComponentMeta's "full reference" listing 10 of 16; QueryParams 6 of 9; the nav
union 7 of 9; the plugin catalogue naming a package that was never built.

Second class: phantom exports. `@object-ui/mobile` was documented with seven
names it does not export, and `@object-ui/app-shell` with three `*Renderer`
names that are either nowhere in the repo or belong to other packages.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

skills-sweep ⑫: skills/objectui (5,686 lines) — behavioral-claim verification, the program's last package

2 participants