Skip to content

docs(core): drop the expired formatPercent citation from the percent-route comments - #5606

Merged
os-sales merged 2 commits into
mainfrom
claude/issue-4596-formatpercent-comments
Aug 21, 2026
Merged

docs(core): drop the expired formatPercent citation from the percent-route comments#5606
os-sales merged 2 commits into
mainfrom
claude/issue-4596-formatpercent-comments

Conversation

@os-sales

@os-sales os-sales commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Fixes #4596

Prose only. Three comments in packages/core/src/utils cited formatPercent as the live example of the divide-by-100 percent route. Each was accurate when written and stopped being true once objectui#4590 (PR #4595) landed: formatPercent renders through style: 'percentPoints' with no division, so a reader checking the citation found the opposite of what it claimed.

The surrounding argument is untouched. It is the measured case for the percentage-points route, and the tie / extreme-magnitude pins under it are what keep that route honest. Only the "who does it the other way" example expired.

⚠️ Retraction, left visible rather than swapped out

An earlier revision of this PR body and of commit c9ea977a1 claimed the change was "provably zero emitted JavaScript" and that the compiled artefact was byte-identical. That claim was false, and the measurement behind it was invalid. It came from a hand-run transpile with removeComments: true — a flag that overrode the single setting which decides the question. tsconfig.base.json:22 sets removeComments: false deliberately, and @object-ui/core builds with a plain tsc, so a comment inside a function body is emitted into dist. Re-measured with the package's real build below. The empty-frontmatter changeset still stands, but on different and better ground — see the changeset section. Commit 6cb62f862 carries the correction.

The replacement claim, confirmed by grep and not inherited

The card's own replacement sentence is that the repo now has no caller taking the divide-by-100 route. This card exists because a confident comment went stale, so the claim was checked before being written down rather than copied across. The check changed the sentence: there are still live style: 'percent' call sites, and the honest claim is narrower than the card's.

Probes, run over packages/ apps/ examples/ e2e/ scripts/ eslint-rules/, excluding node_modules/ and dist/:

probe hits reading
a division by 100 paired with style: 'percent' on one line 2 both are comment prose, in percent-tie-halfup-4590.test.ts and this card's own test file, describing the route as the one NOT taken
files containing both a / 100 and a style: 'percent' 4 all four are the percent-route pin suites plus fields/src/index.tsx, whose match is the comment explaining why the division is gone
executable (non-comment) lines with style: 'percent' 8 triaged below
other spellings of the same scaling (* 0.01, / 1e2, / 100.0) 0 / 0 / 0 no alternate route

Control probes on terms known to be present, so a zero-hit above is a measurement and not a broken search: percentPoints 31, style: 'percent' 26, formatPercent 121, / 100 39.

Triage of the 8 executable style: 'percent' sites:

  • 2 production, neither dividing. packages/components/src/renderers/basic/elements.tsx:348 (element:number's FORMAT_OPTS) and packages/plugin-report/src/LiveReportExporter.ts:305 (inferLocaleOptions for the Excel export) both hand Intl the raw value, i.e. a FRACTION. That is style: 'percent''s own contract, not the divide-by-100 trap.
  • 6 in tests. Five are the percent pin suites; packages/i18n/src/__tests__/spec-formatters.test.ts:111 passes 0.75, a fraction. The only executable divide-by-100 pairing left in the repo is packages/core/src/utils/__tests__/number-display.percent-points.test.ts:114-117, which constructs the route deliberately and asserts .not.toBe(expected) — a pin showing it disagreeing, not a caller.

So the sentence written into both comments is that no caller takes the divide-by-100 route today, with the two remaining style: 'percent' sites named as fraction-holders, and the route recorded as surviving only where a test builds it to show it disagreeing. That is checkable, and it names what would falsify it.

The two counts now name their grid

27,581 / 1,200,013 and 27,577 / 1,200,003 are both correct and neither was silently swapped. Naming "the grid" alone does not separate them — both cards describe the same grid (0.005 steps to 2,000, precisions 0/1/2). The disambiguator is the call shape measured, so the comments now state both:

Each site keeps the count that belongs to it and points at the other as a different form set rather than a correction.

A third site of the same defect, declared

The card named two sites. A sweep of every formatPercent mention in the two files turned up a third, eight lines below the header being repaired, in the same file: the percentAffix helper's comment claimed Intl's style: 'percent' is "the SAME source formatPercent renders through". After objectui#4590 the two reach the same CLDR convention by different Intl options.

It is corrected in place rather than left: same defect class, same file already open, one clause, and the correct form is pinned by existing measured evidence (the 171-locale affix parity restated in formatPercent's own comment). Leaving a known-stale formatPercent citation in the very file whose header is being repaired for stale formatPercent citations would reproduce the failure this card exists to stop.

The other four formatPercent mentions in these files were checked and are accurate: two are past-tense history, two describe what formatPercent still is.

Diff shape — re-measured with the real build

No source line outside a comment changed, and that part was and remains sound: non-comment added lines 0, non-comment removed lines 0.

The shipped bytes DO move. Built at HEAD and at HEAD~1 with packages/core/dist and the tsbuildinfo cleared between runs:

                                        before        after         verdict
dist/utils/dataset-format.js            14,716 B      15,457 B      CHANGED
                                        35d28556…     1e00cc5c…
dist/utils/dataset-format.d.ts          10,580 B      10,580 B      byte-identical
                                        be5f5938…     be5f5938…
dist file count                         180           180           unchanged

dist/utils/dataset-format.js is the only one of the package's 180 dist files that moves. The declaration is byte-identical, because the edited comment sits inside formatMeasure's body and never reaches the .d.ts. The test file is excluded from core's build program (src/**/__tests__/**, per that tsconfig's own comment) and never reaches dist at all — grep -c __tests__ over the emitted file list is 0.

Restore leg run and verified, not skipped: after rebuilding the HEAD~1 revision, the files were restored from HEAD and rebuilt, reproducing the AFTER tree hash exactly (fe020e3764bae607, 180/180 files) with a clean git status. The A/B was confirmed on disk each way — the string way formatPercent does counted 1 in the before leg and 0 after restore — so neither leg was a no-op edit.

No new test, and no ablation — stated plainly rather than ritualised. A prose change has nothing to assert on, and a comment cannot be ablated: removing it changes no observable behaviour, so there is no red leg to run. The existing pins are the relevant evidence and they are unmoved.

Changeset — empty frontmatter, on the ground that actually decides it

An empty-frontmatter changeset, which the presence gate accepts as a complete answer and prints so explicitly:

2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)
Every one of them has an EMPTY frontmatter - declared as releasing nothing, which
is the explicit exemption and a complete answer to this gate.

Why it is right here is not byte identity — the bytes move, as measured above. It is right because both comments are internal reasoning, not consumer-visible API documentation: one sits inside formatMeasure's body, the other is a test-file header. No released behaviour changes, and the declaration is byte-identical, so nothing a consumer types against or reads on hover moves. That argument holds regardless of what the emitted .js does, which is why it is the sounder ground.

AGENTS.md names the empty form as the first-class declaration for a pure-internal change ("要的是『声明一次』,不是强制发版"), and .changeset/dead-refresh-callback-objectview.md is direct precedent for a package src/ change declared empty on "internal only, no released behaviour change". A patch would publish the whole 39-package fixed group and, since the platform reads these changesets into its release notes, put a user-facing release note in front of people for a change with no consumer-visible surface.

The contrasting case, for the reader: a JSDoc header on an exported function would take a patch, because there the comment is the deliverable and consumers reading it on hover is the point.

Gates

Exit codes captured before any pipe; each gate's own verdict line quoted. Re-run at 6cb62f862, the final commit.

gate exit its own verdict line
pnpm --filter @object-ui/core type-check 0 tsc --noEmit && tsc -p tsconfig.test.json (clean)
pnpm --filter @object-ui/core lint 0 513 problems (0 errors, 513 warnings)
pnpm exec vitest run packages/core/ (repo root) 0 Test Files 93 passed (93) / Tests 1945 passed (1945)
pnpm --filter '@object-ui/core^...' build 0 closure clean; core's own tsc build clean on all three legs above
check-control-bytes.mjs 0 OK (scanned 4654 tracked text file(s); skipped 85 binary)
check-changeset-presence.mjs 0 2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)
check-changeset-no-major.mjs 0 No changeset declares a "major" bump.
check-changeset-fixed.mjs 0 All workspace packages are in the changeset fixed group.
check-type-check-coverage.mjs 0 type-check coverage: 45/46 via type-check ... 1 not compiled
check-lint-coverage.mjs 0 lint coverage: 46/46 packages linted, 0 with outstanding errors (0 total)
check-doc-component-types.mjs 0 Every documented component type is registered.
check-doc-links.mjs 0 Links are valid across 13 scan roots.
check-skills-paths.mjs 0 OK (95/96 stated path(s) resolve across 18 guide file(s); 1 baselined)
check-doc-snippet-types.mjs 1 The snippet program was NOT run: the packages it resolves against are not built

The one non-zero is a broken gauge, not a finding. That gate needs a full workspace build; it names @object-ui/react as unbuilt, a package this diff does not touch. CI builds everything before running it, and its run there is the one that counts.

The gate list was re-derived from the workflow files against this diff rather than taken on trust — packages/core/src/** plus .changeset/** starts ci.yml, lint.yml, control-bytes.yml, changeset-presence.yml, changeset-guard.yml, doc-component-types.yml, doc-snippet-types.yml, docs-links.yml and skills-paths.yml; node-esm-load-gate.yml and published-dist-gate.yml are nightly plus self-path-scoped and do not run on this PR.

vitest ran from the repo root (RUN v4.1.10 /home/user/objectui-4596), not a package cwd, so objectui#3378's silent-false-green is excluded. The 93 files run equal the 93 test files on disk under packages/core, and vitest list collects all 13 tests from dataset-format.percent-convention.test.ts, so the header edit did not disturb collection.

Lint scope: a narrowing, declared and measured

Repo-wide pnpm lint (turbo run lint, 46 packages) is CI's run. Locally it was narrowed to @object-ui/core, which is the identical command CI runs for that package (eslint .) and which contains 100% of the changed source files. The three pieces that make this a measurement rather than a gap:

  1. Population from eslint's own config, not from a guess about which files count: eslint . resolved in packages/core.
  2. File count from --format json: 185 files, 0 errors, 513 warnings. Both changed files are in that population, each at errors=0, warnings=0.
  3. Invariance for untouched files: the other 45 packages have zero changed files, and this diff changes no declaration in any of them, so no untouched file's verdict can move regardless of whether linting is type-aware.

Found on the way, filed not folded in


Generated by Claude Code

…route comments

Three comments in `packages/core/src/utils` cited `formatPercent` as the live
example of the divide-by-100 percent route. Each was accurate when written and
stopped being true when objectui#4590 landed: `formatPercent` renders through
`style: 'percentPoints'` with no division, so a reader checking the citation
found the opposite of what it claimed.

The comments now argue the route on its own merits, and record what replaced the
example: no caller in this repo takes the divide-by-100 route today. Confirmed by
grep rather than inherited -- `formatPercent` was the last caller, the two
remaining `style: 'percent'` sites (`element:number`'s format options, the report
exporter's Excel options) hand `Intl` a FRACTION, which is that style's own
contract, and the route otherwise survives only where a test builds it in order
to show it disagreeing.

The measured argument and the tie / extreme-magnitude pins under it are
untouched. The `27,581 of 1,200,013` figure now names its grid -- objectui#4576's
tie-dense grid, 0.005 steps to 2,000, precisions 0/1/2, on `formatMeasure`'s call
shape -- so it stops reading as a discrepancy against objectui#4590's `27,577 of
1,200,003`, which is the same grid re-measured through `formatPercent`.

Comment-only: the emitted JavaScript is byte-identical for both files.

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 52 chunks) 3785.7 KB 3867.2 KB
Main entry chunk (gzip) 151.5 KB 350 KB
Entry file index-B-xm0qe1.js
Status PASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 10.04KB 3.72KB
app-shell (runtime-config.js) 8.91KB 2.99KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 29.34KB 7.05KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 6.35KB 2.43KB
auth (index.js) 2.77KB 1.22KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.02KB 0.89KB
auth (useIsWorkspaceAdmin.js) 3.04KB 1.45KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 506.99KB 113.73KB
core (index.js) 4.51KB 1.80KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 159.80KB 44.33KB
fields (index.js) 238.85KB 60.13KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.44KB 1.39KB
i18n (pickLocalized.js) 7.22KB 3.08KB
i18n (provider.js) 23.13KB 7.63KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 33.40KB 8.71KB
i18n (useSafeTranslation.js) 7.77KB 3.13KB
layout (index.js) 38.95KB 10.97KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.55KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useResponsiveConfig.js) 1.37KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 9.35KB 3.31KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 4.42KB 1.42KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 1.81KB 0.83KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 46.62KB 12.83KB
plugin-charts (index.js) 64.72KB 18.35KB
plugin-chatbot (index.js) 181.21KB 43.14KB
plugin-dashboard (index.js) 128.53KB 32.97KB
plugin-designer (index.js) 212.30KB 42.80KB
plugin-detail (index.js) 242.15KB 60.89KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 125.07KB 30.43KB
plugin-gantt (index.js) 164.10KB 39.87KB
plugin-grid (index.js) 200.79KB 54.26KB
plugin-kanban (index.js) 52.93KB 14.60KB
plugin-list (index.js) 111.70KB 27.17KB
plugin-map (index.js) 20.06KB 6.62KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 43.49KB 11.93KB
plugin-timeline (index.js) 26.68KB 7.66KB
plugin-tree (index.js) 8.50KB 2.88KB
plugin-view (index.js) 84.50KB 20.68KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 43.66KB 14.77KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.33KB 0.69KB
react (schema-input.js) 1.45KB 0.83KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (index.js) 4.77KB 2.16KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 10.76KB 3.17KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 6.92KB 2.40KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.87KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-retry.js) 4.32KB 2.02KB
types (index.js) 3.08KB 1.53KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.05KB 1.93KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 3.40KB 1.71KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

…ange

Retraction, in place rather than silent. The previous commit's changeset and
message claimed the emitted JavaScript was byte-identical. That measurement was
wrong: it was taken with a hand-run transpile using `removeComments: true`, which
overrode the one setting that decides the question.

`tsconfig.base.json` sets `removeComments: false` deliberately, and
`@object-ui/core` builds with a plain `tsc`, so a comment inside a function body
IS emitted. Re-measured with the package's real build, `dist/` cleared and the
tsbuildinfo removed between runs:

  dist/utils/dataset-format.js    14,716 -> 15,457 bytes, sha 35d28556 -> 1e00cc5c
  dist/utils/dataset-format.d.ts  byte-identical, sha be5f5938, 10,580 bytes
  180 dist files both sides; dataset-format.js is the only one that moves
  restore leg rebuilds the AFTER tree hash exactly, working tree clean

The test file is excluded from core's build program (`src/**/__tests__/**`) and
never reaches `dist`.

The empty-frontmatter declaration still stands, on better ground: both comments
are internal reasoning -- one inside `formatMeasure`'s body, one a test-file
header -- not consumer-visible API documentation. No released behaviour changes
and the declaration is byte-identical, so nothing a consumer types against or
reads on hover moves. Shipped bytes moving is not the criterion.

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 52 chunks) 3785.7 KB 3867.2 KB
Main entry chunk (gzip) 151.5 KB 350 KB
Entry file index-DgLUz_sC.js
Status PASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 10.04KB 3.72KB
app-shell (runtime-config.js) 8.91KB 2.99KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 29.34KB 7.05KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 6.35KB 2.43KB
auth (index.js) 2.77KB 1.22KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.02KB 0.89KB
auth (useIsWorkspaceAdmin.js) 3.04KB 1.45KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 506.99KB 113.73KB
core (index.js) 4.51KB 1.80KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 159.80KB 44.33KB
fields (index.js) 238.85KB 60.13KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.44KB 1.39KB
i18n (pickLocalized.js) 7.22KB 3.08KB
i18n (provider.js) 23.13KB 7.63KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 33.40KB 8.71KB
i18n (useSafeTranslation.js) 7.77KB 3.13KB
layout (index.js) 38.95KB 10.97KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.55KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useResponsiveConfig.js) 1.37KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 9.35KB 3.31KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 4.42KB 1.42KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 1.81KB 0.83KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 46.62KB 12.83KB
plugin-charts (index.js) 64.72KB 18.35KB
plugin-chatbot (index.js) 181.41KB 43.22KB
plugin-dashboard (index.js) 128.36KB 32.95KB
plugin-designer (index.js) 212.30KB 42.80KB
plugin-detail (index.js) 242.15KB 60.89KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 125.07KB 30.43KB
plugin-gantt (index.js) 164.10KB 39.87KB
plugin-grid (index.js) 200.79KB 54.26KB
plugin-kanban (index.js) 52.93KB 14.60KB
plugin-list (index.js) 111.70KB 27.17KB
plugin-map (index.js) 20.06KB 6.62KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 43.49KB 11.93KB
plugin-timeline (index.js) 26.68KB 7.66KB
plugin-tree (index.js) 8.50KB 2.88KB
plugin-view (index.js) 84.50KB 20.68KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 43.66KB 14.77KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.33KB 0.69KB
react (schema-input.js) 1.45KB 0.83KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (index.js) 4.77KB 2.16KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 10.76KB 3.17KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 6.92KB 2.40KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.87KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-retry.js) 4.32KB 2.02KB
types (index.js) 3.08KB 1.53KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.05KB 1.93KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 3.40KB 1.71KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Copy link
Copy Markdown
Collaborator Author

PM review — ACCEPT (card #4596)

Gates. 22 named check runs read individually for completed + success at 6cb62f862: 19 success, 3 skippedTest (coverage), the unexpanded Test (coverage shard …/4) matrix placeholder, and dependabot. All four real shards, Type Check, Lint, Build & E2E, Build Docs, Bundle Analysis, Live E2E, both doc type checks, Control Byte Scan, Skill Guide Path Check, Internal Docs Link Check and all three changeset checks green. Note that check-doc-snippet-types — which exited 1 locally as a self-declared broken gauge for want of a workspace build — is green here, which is the run that counts.

The dispatch was wrong and the grep said so

I handed this card's replacement sentence down as "the better claim and the one to write": that the repo has no caller taking the divide-by-100 route. The grep did not support it. Live style: 'percent' callers do remain — they hand Intl a fraction, which is that style's own contract rather than the trap the comment warns about.

Writing the narrower, supported sentence instead, naming the two survivors as fraction-holders and recording that the divide-by-100 route now lives only where a test builds it to show it disagreeing, was the correct call. Taking my sentence on authority would have reproduced this card's own failure mode one level down: a confident unqualified claim that the next reader checks and finds contradicted by two live call sites. Ruled A.

The negative results are measurements rather than broken commands, which is what lets them carry a deletion: * 0.01 = 0, / 1e2 = 0, / 100.0 = 0, each against controls on terms known present — percentPoints 31, style:'percent' 26, formatPercent 121, / 100 39. All eight executable style: 'percent' sites were then triaged individually rather than counted.

A third site, and a fence I drew at the wrong granularity

A third stale formatPercent citation sat eight lines below the header being repaired, in the same fenced file. Fixing it in place was right, and the flag was fair: my dispatch named "the file header", i.e. it fenced at line level.

The fault is mine and worth naming precisely — I fenced the two known instances when the deliverable was "no stale formatPercent citations in these two files." Leaving a known-stale citation inside a file being repaired for stale citations would have reproduced the defect and invited a duplicate card. Ruled A, kept.

The changeset: right answer, wrong support, now fixed

The empty-frontmatter form stands. But the first justification was measured with --removeComments, which overrides tsconfig.base.json:22's deliberate "removeComments": false — the same trap that caught #5602 independently in this same round.

Re-measured with the real tsc build, the correction is better than the argument I proposed. I argued qualitatively that these are internal reasoning comments rather than consumer-visible API docs; this turned that into a mechanical test:

  • dist/utils/dataset-format.js 14,716 → 15,457 B — the only one of 180 emitted files that moves
  • dist/utils/dataset-format.d.ts byte-identical, 10,580 B, sha be5f5938 on both legs, because the edited comment sits inside a function body and never reaches the declaration
  • the test file is excluded from core's build program, so it ships nothing

The .d.ts being byte-identical is "no consumer-visible surface changed", stated as a measurement. And the asymmetry that decided it holds regardless: a wrong patch publishes the fixed group and puts a user-facing release note in front of people for a change with no behaviour, irreversibly; a wrong empty costs a one-line edit.

For contrast, #4622 landed the opposite call — patch — on the same criterion, because there the JSDoc is on an exported function and does reach the .d.ts. Same test, two different answers, both correct.

The restore leg was run and verified to reproduce the AFTER treehash exactly rather than assumed, and the mutation was confirmed on disk in both directions.

Findings

#5607 is the valuable one and is not observation-class: (value / 100) * 100 re-introduces the artefact one call frame upstream, with 19,978 of 199,000 values changing bit pattern and 1,108 rendered strings moving. Correctly kept out of this PR — different package, outside the fence. Labelled and put in front of triage as a candidate for a coming batch. #5608 records the same discrepancy-reading surviving at two sites outside this fence.


Generated by Claude Code

@os-sales
os-sales marked this pull request as ready for review August 21, 2026 17:45
@os-sales
os-sales added this pull request to the merge queue Aug 21, 2026
Merged via the queue into main with commit 6606337 Aug 21, 2026
22 checks passed
@os-sales
os-sales deleted the claude/issue-4596-formatpercent-comments branch August 21, 2026 17:45

Copy link
Copy Markdown
Collaborator Author

Non-blocking addendum — already enqueued, nothing to change. Recording it because it is the third time this round a dev's measurement corrected my dispatch, and because it closes the arithmetic the card was actually about.

My dispatch instruction was ineffective as written. I said to "name the grid alongside whichever count you keep, so the two stop reading as a discrepancy." You found naming the grid does not separate them, and used the call shape instead. You were right, and the reason is visible in the numbers:

400,001 × 3 = 1,200,003

That is exactly #4590's total — 0.005 steps to 2,000 inclusive, times precisions 0/1/2. So the stepping scheme really is the same in both measurements, and my "different grids" framing (inherited from the card body) was the wrong axis. The disambiguator is what was measured through, exactly as you wrote it.

Which also means the leftover 1,200,013 − 1,200,003 = 10 is not noise: #4576's form set carries ten forms beyond the stepped grid. Your committed wording covers this correctly by calling them different form sets rather than a correction, so the shipped comments are sound as they stand.

Worth stating explicitly here, though, because a future reader doing the same subtraction will land on that 10 and want to know whether it is a discrepancy — and "ten extra forms in the earlier set, same stepping" is the answer that stops the question recurring. If anyone later touches these lines, that is the sentence to add; it is not worth a CI cycle on its own.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[core] Two comments still say formatPercent divides by 100 — true until #4590, false after it

2 participants