Skip to content

fix(i18n): every count label a word agrees with is an i18next count family in all ten packs — no === 1 key switch, no *Plural / *One pair (objectui#11445) - #11456

Merged
objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/issue-11445-count-families
Oct 2, 2026
Merged

objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/issue-11445-count-families

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #11445

Clause-②: no

What this changes

Every count label a word must agree with is now an i18next count family, in all ten packs. No component chooses a plural key itself any more.

  • 68 keys become families. 15 code-selected pairs (xxxCountOne / xxxCountPlural, chosen on === 1) and 53 single-form count keys. Each pack spells every CLDR category its language selects: en _one / _other, ru _one / _few / _many / _other, ar all six, and the others whatever Intl.PluralRules selects. Base values are unchanged, so check:i18n-drift reads 0 en values changed, and the base still answers a call made without a count.

  • The 15 siblings leave all ten packs. The components call t(key, { count }), and every === 1 key switch is gone. Eight call sites passed count: String(n), which i18next does not plural-select; they pass the number now. These are the four collaboration count labels, the three collaboration relative timestamps, and the import wizard's auto-match summary. The page:tabs badge passes the number below 1000; a shortened 1.2k stays a string, so it reads the count-invariant base key.

  • The provider-less paths read families the way i18next does. createSafeTranslation's fallbackT, and the gantt and import-wizard hand-rolled fallbacks, look up key_CATEGORY (English rules) for a numeric count, then the base row. Without this, retiring the pairs would have regressed provider-less hosts from "1 reply" to "1 replies". The defaults tables carry each converted family's _one / _other rows where English differs.

  • The pin. The new packages/i18n/src/__tests__/count-families-11445.test.ts sits beside the objectui#11432 pin. It fails in two cases:

    • (a) any pack holds a …Plural key;
    • (b) any {{count}} leaf, in any pack, is outside a family and not on its count-neutral list. Each list entry carries its reason.

    The list can only shrink: every entry must still exist, still interpolate {{count}} and still sit outside a family. objectui#11432's two pins judge the 68 new families by construction, for slot existence and i18next selection.

Premise check, on current main

The card's measurement reproduces at 89cad75d5, the first cut, and again at 37140f4f5 after merging main (plain i18next 26.4.0 over the shipped packs, emulating each call site's === 1 switch):

  • ru detail.attachmentCount: 3 вложений, 21 вложений. ru detail.replyCount: 21 ответов.
  • ar detail.attachmentCount: 2 مرفقات, 100 مرفقات. ar detail.replyCount: 11 ردود.
  • ru at 3: Согласовать 3 запросов?, Импортировать 3 строк, 3 строк изменено, 3 ожидающих утверждений. en: 2 row modified.

The card's "the defective set needs a per-key read" held, and the population is larger than its sample. At the merge base, 130 {{count}} leaves sat outside the 14 families. Read pack by pack, with each call site's reachable range:

  • 68 needed a family. A word agreed with the number in at least one pack, often de, es, pt or fr at 1, or ar with a full noun.
  • 48 are count-neutral.
  • 15 were siblings that retire.
  • 1 is not a count.

The census enumerates every t(…, { count }) call site: 158 literal-key sites. 109 now reach a family and 47 reach a count-neutral key. The other 2 are scanner false matches: a variable named count passed as total, and a key named report.aggregate.count. No call site passes count to a key without a hole or a family.

Census — every count label, by key

1. Code-selected sibling pairs — 15 converted, 1 not a count, 1 unreached

Family key Retired sibling Call site(s), now t(key, { count })
lookup.recordCount lookup.recordCountOne RecordPickerDialog.tsx
common.itemCount common.itemCountOne containers.tsx
list.recordCount list.recordCountOne ListView.tsx
detail.reactionCount detail.reactionCountOne ReactionPicker.tsx
detail.attachmentCount detail.attachmentCountPlural CommentAttachment.tsx
detail.replyCount detail.replyCountPlural ThreadedReplies.tsx
console.objectView.recordCount console.objectView.recordCountOne ObjectView.tsx
search.resultsCount search.resultsCountPlural SearchResultsPage.tsx
search.itemsAvailable search.itemsAvailableOne SearchResultsPage.tsx
ai.formAssist.suggestionCount ai.formAssist.suggestionCountOne AIFormAssist.tsx
ai.formAssist.appliedCount ai.formAssist.appliedCountOne AIFormAssist.tsx
collaboration.commentCount collaboration.commentCountOne CommentThread.tsx (the call passed a string count; now a number)
collaboration.reactionCount collaboration.reactionCountOne CommentThread.tsx (the call passed a string count; now a number)
collaboration.presentUserCount collaboration.presentUserCountOne PresenceAvatars.tsx (the call passed a string count; now a number)
collaboration.moreUserCount collaboration.moreUserCountOne PresenceAvatars.tsx (the call passed a string count; now a number)
fields.location.refusedResidue …One kept NOT a count: the two values interpolate different holes (one half, or both halves), selected by which halves exist; no {{count}}
detail.relatedRecords detail.relatedRecordOne kept unreached: no t() caller (check:i18n-dead-keys lists both); on the count-neutral list as unreached

2. Single-form count keys — 53 converted

Key Pack(s) whose single form disagreed with the number Call site(s)
fields.relativeDate.overdue ar (3-7 days need the plural noun) date-display.ts (core, options.t)
table.modified en, de, es, ru data-table.tsx
table.selected es (1 seleccionados) data-table.tsx, LookupField.tsx, PeoplePicker.tsx, RecordPickerDialog.tsx
grid.bulkSelected es, pt BulkActionBar.tsx
grid.bulkAllOnPage en, de, fr, es, pt, ru at a page size of 1 BulkActionBar.tsx
grid.import.autoMatchedSummary ar ImportWizard.tsx (string count → number)
grid.import.rowsWithErrors ru ImportWizard.tsx
grid.import.rowsCorrected ru ImportWizard.tsx
grid.import.resultsTruncated ar (صفًا agrees only with 11-99; the cap is the server's) ImportWizard.tsx
grid.import.createdCount es, pt ImportWizard.tsx
grid.import.updatedCount es, pt ImportWizard.tsx
grid.import.moreErrors en, de, ru ImportWizard.tsx
grid.import.errorCount en, fr, es, pt ImportWizard.tsx
grid.import.importNRows en, de, fr, es, pt, ru, ar ImportWizard.tsx
grid.bulk.confirmDefault de BulkActionDialog.tsx
grid.bulk.skippedIneligible de, fr, es, pt, ru (verb agreement) BulkActionDialog.tsx, ListView.tsx
gantt.conflict.body de GanttView.tsx
gantt.autoScheduleDlg.body de, ru (задач(и)) GanttView.tsx
gantt.autoScheduleDlg.skipped de, ru GanttView.tsx
detail.minutesAgo ar AiPendingActionsInbox.tsx, ActivityTimeline.tsx, RecordActivityTimeline.tsx, RecordComments.tsx, RecordMetaFooter.tsx, ThreadedReplies.tsx
detail.hoursAgo ar AiPendingActionsInbox.tsx, ActivityTimeline.tsx, RecordActivityTimeline.tsx, RecordComments.tsx, RecordMetaFooter.tsx, ThreadedReplies.tsx
detail.daysAgo ar AiPendingActionsInbox.tsx, ActivityTimeline.tsx, RecordActivityTimeline.tsx, RecordComments.tsx, RecordMetaFooter.tsx, ThreadedReplies.tsx
detail.showEmptyFields en, de, fr, es, pt, ru DetailSection.tsx
console.saveAdvisoryTitle ar saveAdvisoryToast.ts
console.publishAdvisoryTitle ar saveAdvisoryToast.ts
console.objectView.importedToast ru ObjectView.tsx
console.objectView.bulkDeleteConfirm en, de, fr, es, pt, ru ObjectView.tsx
home.pendingDrafts.message ru HomePage.tsx
preview.changes.confirmNote en, de, fr, es, pt, ru DraftChangesPanel.tsx
preview.changes.securityBlockTitle ru (элемент(ов)) DraftChangesPanel.tsx
objectActions.bulkDeleteSuccess ru (rendered at 2 or more) recordDelete.ts
notifications.groupUnread fr, pt InboxPopover.tsx
notifications.approvalsPending en, de, fr, es, pt, ru, ar HomeRail.tsx, InboxPopover.tsx
notifications.arrivalMany ar (rendered at 2 or more) useInboxArrivalNotifier.ts
notifications.arrivalRepeats ar (rendered at 2 or more) useInboxArrivalNotifier.ts
excelImport.imported ar ExcelImportBar.tsx
marketplace.detail.purgeSuccess ar MarketplacePackagePage.tsx
marketplace.detail.reseedPartialErrors ar MarketplacePackagePage.tsx
marketplace.relativeTime.monthsAgo de, es, pt MarketplacePage.tsx
marketplace.relativeTime.yearsAgo de, fr, es, pt, ru MarketplacePage.tsx
approvalsInbox.remindSuccess ar RecordApprovalsPanel.tsx
approvalsInbox.progressEligible de, ar ApprovalsInboxPage.tsx, RecordApprovalsPanel.tsx
approvalsInbox.actionableCount fr, es, pt ApprovalsInboxPage.tsx
approvalsInbox.bulkApproveTitle en, de, fr, es, pt, ru, ar ApprovalsInboxPage.tsx
approvalsInbox.bulkRejectTitle en, de, fr, es, pt, ru, ar ApprovalsInboxPage.tsx
approvalsInbox.bulkApproved en, de, fr, es, pt, ar ApprovalsInboxPage.tsx
approvalsInbox.bulkRejected en, de, fr, es, pt, ar ApprovalsInboxPage.tsx
approvalsInbox.minutesAgo ar ApprovalsInboxPage.tsx
approvalsInbox.hoursAgo ar ApprovalsInboxPage.tsx
approvalsInbox.daysAgo de, fr, es, pt, ar ApprovalsInboxPage.tsx
collaboration.minutesAgo ar CommentThread.tsx (string count → number)
collaboration.hoursAgo ar CommentThread.tsx (string count → number)
collaboration.daysAgo ar CommentThread.tsx (string count → number)

3. {{count}} outside a family — 48, each on the pin's count-neutral list

Reason Keys (call sites)
tally perm.facet.more (PermissionFacetLink.tsx); lookup.showAllResults (LookupField.tsx); fields.textarea.characterCount (character-count.tsx); fields.textarea.charactersRemaining (character-count.tsx); table.totalRecords (none (a data-table.tsx defaults row only)); table.saveAll (data-table.tsx); grid.bulk.overLimit (BulkActionDialog.tsx); grid.bulk.affectedRecords (BulkActionDialog.tsx); grid.bulk.andMore (BulkActionDialog.tsx); grid.bulk.processed (BulkActionDialog.tsx); grid.bulk.processedFailed (BulkActionDialog.tsx); grid.bulk.succeeded (BulkActionDialog.tsx); grid.bulk.resultFailed (BulkActionDialog.tsx); calendar.moreEvents (CalendarView.tsx); calendar.unscheduled (ObjectCalendar.tsx); view.moreViews (ViewTabBar.tsx); detail.showDiscussion (RecordChatterPanel.tsx); console.ai.buildDoctorDrawer.timeline (BuildDebugDrawer.tsx); marketplace.installedCount (MarketplacePage.tsx); approvalsInbox.approveN (ApprovalsInboxPage.tsx); approvalsInbox.rejectN (ApprovalsInboxPage.tsx)
invariant grid.import.imported (ImportWizard.tsx); grid.import.skippedCount (ImportWizard.tsx); console.ai.pendingDrafts.count (PendingDraftsBar.tsx); console.ai.buildDoctorDrawer.allLive (BuildDebugDrawer.tsx); console.ai.buildDoctorDrawer.discrepancies (BuildDebugDrawer.tsx); console.ai.buildDoctorDrawer.issueCount (BuildDebugDrawer.tsx); console.ai.buildDoctorDrawer.platformNoise (BuildDebugDrawer.tsx); home.pendingDrafts.publishedVerified (usePublishAllDrafts.ts); home.pendingDrafts.capabilityWarn (usePublishAllDrafts.ts); preview.history.items (CommitTimeline.tsx)
abbreviated console.ai.minutesAgo (ConversationsSidebar.tsx); console.ai.hoursAgo (ConversationsSidebar.tsx); console.ai.daysAgo (ConversationsSidebar.tsx); layout.activityFeed.relativeSecondsAgo (ActivityFeed.tsx); layout.activityFeed.relativeMinutesAgo (ActivityFeed.tsx); layout.activityFeed.relativeHoursAgo (ActivityFeed.tsx); layout.activityFeed.relativeDaysAgo (ActivityFeed.tsx); marketplace.relativeTime.daysAgo (MarketplacePage.tsx)
twoOrMore grid.bulkSelectedAllMatches (BulkActionBar.tsx); grid.bulkSelectAllMatching (BulkActionBar.tsx); grid.bulkAllMatchingSelected (BulkActionBar.tsx); notifications.groupCount (InboxPopover.tsx)
unreached detail.relatedRecords (none); detail.relatedRecordOne (none); appDesigner.objectManager.fieldCount (none); console.objectView.toolbarEnabledCount (none); layout.appSwitcher.appsAvailable (none)

4. Families since objectui#11432 — 14, unchanged

calendar.a11y.dayCell, chatbot.plan.countDashboards, chatbot.plan.countObjects, chatbot.plan.countViews, console.ai.usage.resetsWeeklyDays, console.ai.usage.resetsWeeklyHours, console.settingsView.unsavedCount, detail.fileCount, detail.repeaterItemCount, detail.showEmptyRelated, perm.facet.fields, perm.facet.objects, perm.facet.rls, perm.facet.tabs — their call sites already pass a numeric count.

The count-neutral reasons, as the pin states them:

  • tally: the number stands alone, so nothing agrees with it. It sits in parentheses, after +, beside / {{total}}, or after a label or verb.
  • invariant: every pack phrases the label count-invariantly. That means an (s) marker in en/de/fr/es/pt (the convention perm-home-namespace-3546.test.tsx pins), a label, partitive or impersonal form in ru/ar, and a counter in zh/ja/ko.
  • abbreviated: a relative time with an abbreviated unit in every pack.
  • twoOrMore: the call site renders the label only at 2 or more. Every pack's form is right at every such count: the cross-page banner needs totalMatching above pageSize, and the inbox group header needs a group of two.
  • unreached: no t() caller; check:i18n-dead-keys lists each one.

Before / after, measured

Plain i18next 26.4.0 over the packs. BEFORE emulates today's call sites (=== 1 switch for the pairs); AFTER is t(key, { count }).

Key lang before after
detail.attachmentCount ru 3 / 21 3 вложений / 21 вложений 3 вложения / 21 вложение
detail.attachmentCount ar 2 / 11 / 100 2 مرفقات / 11 مرفقات / 100 مرفقات مرفقان (2) / 11 مرفقًا / 100 مرفق
detail.replyCount ru 2 / 21 2 ответов / 21 ответов 2 ответа / 21 ответ
detail.replyCount ar 2 / 11 2 ردود / 11 ردود ردّان (2) / 11 ردًّا
approvalsInbox.bulkApproveTitle ru 3 / 21 Согласовать 3 запросов? / … 21 запросов? Согласовать 3 запроса? / Согласовать 21 запрос?
grid.import.importNRows ru 3 / ar 2 Импортировать 3 строк / استيراد 2 صف Импортировать 3 строки / استيراد صفين (2)
table.modified en 2 / ru 3 2 row modified / 3 строк изменено 2 rows modified / Изменено 3 строки
notifications.approvalsPending ru 3 / en 1 3 ожидающих утверждений / 1 pending approvals 3 ожидающих утверждения / 1 pending approval

Reverse verification and ablation

Each prediction was written before its run.

  • The fix at the merge base, this branch's tests at HEAD. The 31 source files were set to their 37140f4f5 blobs (31 of 31 confirmed by hash), and 7 test files were run.
    • Predicted 65 failing tests in 5 files: count-families 3, useSafeTranslation 5, countLabels-10242 16, countLabels-10425 29, SearchResultsPage-10024 12. Predicted plural-categories-11432 and all-locales-key-parity green.
    • Observed Test Files 5 failed | 2 passed (7), Tests 65 failed | 123 passed (188), split exactly as predicted.
    • The pin printed 30 `…Plural` key(s) (3 keys in 10 packs), 82 count label(s) outside any family (68 bases and the 14 siblings that interpolate {{count}}), and expected 14 to be greater than or equal to 82.
    • Restore: each blob equals HEAD, and git diff HEAD is empty with 0 porcelain lines.
  • Ablations at HEAD, through ablation-replace.mjs: each anchor must hit once, each write is verified on disk, and each restore is proven by blob hash.
    • B, ru detail.attachmentCount_few deleted. Predicted 2 failing. Observed Tests 2 failed | 60 passed (62): parity's ru: 1 plural slot(s) missing: [ 'detail.attachmentCount_few' ], and the i18next pin's ru: 1 slot(s) not selected. The new pin stayed green, as predicted.
    • C1, perm.facet.more taken off the count-neutral list. Predicted 1 failing. Observed Tests 1 failed | 61 passed: 1 count label(s) outside any family.
    • C2, detail.attachmentCountPlural planted back into en. Predicted 11 failing. Observed Tests 11 failed | 51 passed: the pin's 1 `…Plural` key(s): [ 'en:detail.attachmentCountPlural' ], its outside-a-family case, and parity's "defines every en key" for 9 packs.
    • Every leg restored (blob == HEAD, git diff HEAD empty), and the tree reads 0 porcelain lines afterwards. Every subject resolves to src through relative imports or the vitest @object-ui/* aliases, so no dist build or preflight applies.

Gates

The vitest union is every test file that names a converted or retired key, asserts a changed English string, or names a touched component, plus packages/i18n/ in full. It ran in four lock-held batches:

  • B, C and D ran at 558168c53.
  • A ran again at b6800f9b5. The only change since 558168c53 is the body of .changeset/11445-i18n-count-families.md.
  • Patch round 1 (head 6343a6e77) changes changeset prose only: no source, test or locale byte moves, so the vitest, build and type-check rows below stand for this head. The changeset gates were re-run there (rows marked patch round 1).
Check Verdict line
vitest A: packages/i18n/ plus console and scripts tests Test Files 113 passed (113) · Tests 2363 passed | 13 skipped (2376) · VERDICT command-exit 0
vitest B: the other packages' tests Test Files 200 passed (200) · Tests 2824 passed (2824) · VERDICT command-exit 0
vitest C: app-shell, first half Test Files 90 passed (90) · Tests 786 passed (786) · VERDICT command-exit 0
vitest D: app-shell, second half Test Files 91 passed (91) · Tests 1011 passed | 1 skipped (1012) · VERDICT command-exit 0
turbo run build --concurrency=2 over the app-shell and plugin-chatbot dependency closures Tasks: 28 successful, 28 total
type-check (tsc --noEmit && tsc -p tsconfig.test.json): i18n, collaboration, components, fields, plugin-ai, plugin-chatbot, plugin-detail, plugin-gantt, plugin-grid, plugin-list, plugin-view each type-check: Done
type-check: app-shell VERDICT command-exit 0
pnpm check:i18n-keys Every in-scope call-site key resolves against the en pack (3263 keys), every literal inline defaultValue matches the value the pack serves …
pnpm check:i18n-drift 0 en value(s) changed (136 key(s) added, 15 removed — those are all-locales-key-parity's) · No en value changed in this range.
pnpm check:i18n-designer-parity Every en row has a zh row, and every shared row carries the same placeholders.
pnpm check:i18n-dead-keys (report-only census) exit 0 · see the Acceptance notes
pnpm check:control-bytes check-control-bytes: OK (scanned 10016 tracked text file(s); skipped 85 binary).
pnpm check:new-line-citations VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only → exit 0
node scripts/check-changeset-presence.mjs (patch round 1) exit 0 · 54 source file(s) of 12 released package(s) changed, and this change declares 11 changeset(s)
node scripts/check-changeset-no-major.mjs (patch round 1) exit 0 · No changeset declares a major bump.
pnpm check:changeset-claims (report-only, patch round 1) exit 0 · corpus 16 changesets this change adds or modifies, every declared package un-negated. The pending notes it lists name files this branch edits; none is falsified. The ru / ar count-label notes it does not name (9664, 10024, 10242, 10425, 10636) now each end in a dated note naming the count family that replaced their …One / …Plural switch
node scripts/check-changeset-overwrite.mjs (report-only, patch round 1) exit 0 · 11 changeset(s) added, 5 modified, 0 deleted: the five dated-note appends, each with declared at base equal to declares now
check-governed-queue-guard --test NOT GOVERNED — 66 path(s) checked against 5 governed surface(s); none matched.

The type-check ran over the 11 packages at e8d09bfda. Its outer timeout then cut the app-shell leg, which was re-run alone and exited 0. The TypeScript tree is byte-identical at the head. --listFiles counts all 9 changed i18n test files in that package's test program.

eslint over the 54 changed lintable files. With the packages' own eslint . (inline config honoured): 0 errors and 611 warnings, none of them on a line this branch adds (cross-checked against git diff -U0). With --no-inline-config: 1 error, react-hooks/static-components in data-table.tsx, on a line this branch does not touch. Linting the base blob through --stdin gives the same error, from an eslint-disable-next-line the flag ignores.

The narrowing is proven three ways:

  1. Every file is in the linted population: eslint's JSON reports 54 files and no "File ignored" message.
  2. The file count comes from that JSON.
  3. eslint.config.js turns on no type-aware linting (no project / projectService / TypeChecked), and no local rule reads another file. So this diff cannot move a verdict on an untouched file.

Mechanism assumptions, measured

  1. The measured renders. CONFIRMED: the card's renders reproduce on current main; before/after above.
  2. Count-neutral needs a per-key read. CONFIRMED, and wider. The enumeration is the census above, and the defective set is 68 keys, not the card's measured sample of six. Many single forms were wrong outside ru / ar: es / pt participles at 1, de adjectives and verbs at 1, ar full nouns in relative times.
  3. The pin. DONE, as a sibling test, not a gate script. ⚠️ FALSIFIED: "the list stays small". The count-neutral list holds 48 keys under 5 reasons. Each reason is true in all ten packs, and converting any one of those keys would only repeat one string across its slots.
  4. Callers. CONFIRMED and fixed: 8 call sites passed a string count, which i18next does not plural-select; they now pass a number. No caller used another name. The defaults maps carry the families' rows, and createSafeTranslation and the two hand-rolled fallbacks read them.
  5. en single-form values. DONE. Each converted en value gets real _one / _other forms (2 rows modified, 1 pending approval). The base is unchanged, so check:i18n-drift sees no en value move.

Acceptance notes (not filed)

  • check:i18n-dead-keys lists six new slot keys as [confirmed]. They are fields.relativeDate.overdue_one / _other, notifications.arrivalMany_one / _other and notifications.arrivalRepeats_one / _other, and all six are false positives. The census adds suffix spellings only for a call site it parses as a pack t(). These two families are reached through translate(…) (useInboxArrivalNotifier.ts) and options.t?.(key, …) with the key in a const (date-display.ts); their base keys were already found by the text probe. Deleting any of the six would turn all-locales-key-parity, plural-categories-11432 and count-families-11445 red. Owner: none.
  • Five unreached keys stay in the packs. They are detail.relatedRecords / detail.relatedRecordOne (a dead …One pair), appDesigner.objectManager.fieldCount, console.objectView.toolbarEnabledCount and layout.appSwitcher.appsAvailable. No t() caller reaches them, and the dead-key census lists each one. Retiring them is that census's work; a family for strings nobody renders would be dead weight. When one is retired, its count-neutral entry fails the pin's liveness case, which forces the entry out. Owner: none.
  • The metadata-admin engine.* table (packages/app-shell/src/views/metadata-admin/i18n.ts) has its own code-selected …One / …Other labels. That table is en / zh only by its declared carve-out, and a === 1 switch is right for both, so it is outside this card. Owner: none.
  • The base keys of converted families keep their pre-family values. For the *Plural pairs the base is the singular, and some single-form bases keep (s) markers. Under a numeric count a base is reached only when the count is not a number. Changing an en base would force a nine-pack follow under check:i18n-drift for a string no call site renders.
  • Arabic forms follow objectui#11432's shape. Zero and other take the singular, two the dual with the numeral in parentheses, few the plural, and many the accusative singular. Long sentences whose ar form was already count-invariant (a partitive N من … or a label) keep that one form in every slot. A native review is welcome; the pins guarantee each category answers from its own slot, not that the words are idiomatic.

Deviations

  • Install. pnpm install --config.engine-strict=false: node v22.22.0 is below jsdom@30's engines floor. No tracked file changed.
  • File surface beyond the claim's list. All of it follows from the triage shape:
    • (a) packages/i18n/src/useSafeTranslation.ts, plus README.md (a "Count labels are i18next count families" section), and the gantt and import-wizard hand-rolled fallbacks, so provider-less hosts keep the singular.
    • (b) Call sites and comments the enumeration found: ListView.tsx, ObjectView.tsx in both app-shell and plugin-view, AIFormAssist.tsx, CommentThread.tsx, PresenceAvatars.tsx, ReactionPicker.tsx, RecordPickerDialog.tsx, containers.tsx, and LocationField.tsx (a comment that cited the retired convention as precedent).
    • (c) Earlier pins whose assertions this change made false, updated to the families' forms: countLabels 10242 / 10425, SearchResultsPage 10024 / 9664, the ObjectView footer 10636, the collaboration mirrors, the ar relative-time pins (ActivityTimeline 7149, RecordComments 7163, AiPendingActionsInbox 7173), DetailSection 8376 / 8474 (Show 1 empty fields was the English defect itself), the i18n parity siblings (i18n, objectView-config-keys-retired-4730, de-quote-pairing-3876, showEmptyRelated-plural-base-3863, searchItemsAvailable-plural-9664) and the machine-locale census, which gained two declared new Intl.PluralRules('en') sites.
  • One merge of main (37140f4f5, after objectui#11451 landed): clean, no conflicts.
  • Patch round 1: dated notes on five pending entries. .changeset/9664-search-items-available-plural.md, 10024-search-results-count-plural-forms.md, 10242-ru-ar-count-labels.md, 10425-ru-ar-count-labels.md and 10636-objectview-record-count-singular.md each gain a pure-append Dated note, 2026-10-02 — superseded in this release — objectui#11445 paragraph; frontmatter and existing bodies are byte-identical to their base (0 deleted lines). The i18n note's Supersedes paragraph now points at those notes and says the count labels stay only on the base key.
  • Commits end with the model-free trailer pair per objectui AGENTS.md, which takes precedence over the harness's model-named trailer.

Generated by Claude Code

claude added 6 commits October 2, 2026 08:22
…unt families in all ten packs

The 15 code-selected pairs (`xxxCountOne` / `xxxCountPlural`) and the 53
single-form count keys that agreed one noun form with the number in some
pack become i18next count families: every pack spells out each CLDR
category its language selects. The 15 sibling keys leave all ten packs.

Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37
Co-authored-by: Claude <noreply@anthropic.com>
…ch; the pin forbids a third spelling

The 15 code-selected pairs' call sites ask the family with a numeric
`count`; the collaboration and import-wizard call sites that passed a
string count pass the number. `createSafeTranslation`'s provider-less
fallback, and the gantt and import-wizard hand-rolled ones, read a
family's `_one` / `_other` row for a numeric count, the order i18next
reads the `en` pack in, so a provider-less host keeps its "1 reply".

`count-families-11445.test.ts` fails on any `...Plural` key in a pack and
on any `{{count}}` leaf outside a family that is not on its count-neutral
list, each entry with its reason.

Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37
Co-authored-by: Claude <noreply@anthropic.com>
The objectui#10024 / #10242 / #10425 / #10636 / #9664 pins rendered the
count label of each code-selected pair's plural half; they now render the
family slot i18next selects ("2 записи", the ar dual) and assert each key
is a family with no `...One` / `...Plural` sibling. Pins that recorded
the single-form ar relative times and "Show 1 empty fields" read the
family forms; the collaboration and import-wizard mirrors carry the
family rows.

Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 36 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/10031-calendar-user-visible-strings-reach-the-packs.md

  • names all-locales-key-parity.test.ts → packages/i18n/src/__tests__/all-locales-key-parity.test.ts — edited by this change

    ⚠️ A key added to en is not an en-only change in this repo, and that is worth stating because the cheap route looks available: the English fallback does carry the English text at the call site, but the key-set invariant is a pack-vs-pack one — every pack defines every en key — so it is answered by the locale packs, not by the call site. All ten packs therefore carry the twelve new keys, with real translations rather than the English value. The instrument that says so is all-locales-key-parity.test.ts; pnpm check:i18n-drift prints which of the two owns an addition.

.changeset/11432-plural-categories-every-pack.md

  • names all-locales-key-parity.test.ts → packages/i18n/src/__tests__/all-locales-key-parity.test.ts — edited by this change

    all-locales-key-parity.test.ts now computes each family's required slots from Intl.PluralRules per pack, so a new family or a new pack that misses a category fails at PR time, and the parity rule admits a key en lacks only when it is such a slot.

.changeset/4730-objectview-config-keys-retired.md

  • names packages/i18n/src/__tests__/objectView-config-keys-retired-4730.test.ts → packages/i18n/src/__tests__/objectView-config-keys-retired-4730.test.ts — edited by this change

    packages/i18n/src/__tests__/objectView-config-keys-retired-4730.test.ts pins the removal by name. Every i18n gate in this repo runs call site → key, so none of them can see a dead key come back: the parity gate is fully satisfied by 116 dead keys present in all ten packs, and the reverse sweep that found them (scripts/check-i18n-dead-keys.mjs) is report-only by design. The pin is the only thing that would notice.

.changeset/5453-retire-grid-column-wrap-forward.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    generateColumns() copied a per-column wrap onto every column object handed to data-table, and packages/components' data-table.tsx never read it. Measured on the current ref rather than inherited from the card, with comments stripped so prose mentions cannot be counted as reads: a column-level wrap scores 0, against accessorKey 34, align 5, header 4, className 4, width 8 and fitContent 2 in the same query shape. Those sibling counts are the positive control — the search style does find the keys that are genuinely consumed, so the zero is a measurement and not a mis-aimed grep. The raw string wrap does occur in that file; every occurrence is flex-wrap, whitespace-nowrap, or a variable named wrapper.

.changeset/5853-tablecolumn-type-canonical-union.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    @object-ui/types exports TABLE_COLUMN_TYPES (the canonical tuple — the single declaration the zod mirror builds its enum from, so the two cannot drift), the TableColumnType union, and normalizeTableColumnType() for producers. The as any cast in data-table.tsx is deleted and the read is typed, so re-introducing an undeclared spelling is a tsc error rather than a silent widening.

.changeset/5926-empty-action-visible-when.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    The fix is a route, not a new check: no visibleWhen test was added to data-table.tsx. A local check on this slot would have been a fourth evaluator, which is the drift page:tabs' item-level predicate already records on this card. The slot now mounts its authored node exactly the way the empty renderer's action slot always has.

.changeset/5993-button-shared-icon-resolver.md

.changeset/6175-column-state-persistence.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    • @object-ui/components — DataTableSchema has declared onColumnResize?: (columnKey, width) => void all along, and data-table.tsx invoked it nowhere: the resize drag updated the table's local columnWidths state and stopped there. It now reports the settled width once, at mouseup. Once, deliberately — the host turns this callback into a write to shared view config, so a per-mousemove callback would be a write storm. - @object-ui/plugin-grid — ObjectGrid emitted onColumnReorder (singular) while the renderer invokes the near-duplicate onColumnsReorder (with the s), a different declared key with a different signature. The producer now emits the spelling the renderer actually invokes, mapping the reported TableColumn[] to the accessorKey order columnState stores.

.changeset/6373-datatable-emit-boundary.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    The measured read set of the consumer (data-table.tsx, comments stripped) contains none of the six, so all six retire from the emit rather than being declared — declaring a key nothing reads is the same declared != enforced defect facing the other way. Rendering is unchanged because none of those keys was the live path for its own value: the FieldMeta the cell closure captures is what this widget's type-aware rendering has always read, and it is untouched. Authored spellings still pass through, so a column the author wrote as { format: '$0,0' } keeps its format exactly as before.

.changeset/6424-tablecolumn-declares-headericon.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    Scope of the enforcement claim, measured: the declaration, the parse road, and the renderer's behaviour now agree; the renderer's internal column reads remain any-mediated (the col: any normalization in data-table.tsx) — a standing instrument gap, not closed here.

.changeset/6661-app-launcher-nav-menu-renderers.md

  • names en.ts → packages/i18n/src/locales/en.ts — edited by this change

    Three new strings — the launcher's and the menu's accessible names, and the menu's empty state — are declared under console.nav in en.ts and its nine sibling packs. An inline defaultValue alone is not a fix: it renders English at one call site and leaves the string untranslatable everywhere (objectui#3517).

.changeset/6726-find-envelope-records-arms.md

  • names plugin-view/src/ObjectView.tsx → packages/plugin-view/src/ObjectView.tsx — edited by this change

    | module | what it does | | --- | --- | | components/src/hooks/related-count-store.ts | related-list tab badge count | | components/src/renderers/basic/data-list.tsx | element:repeater rows | | components/src/renderers/basic/elements.tsx | element:number client-side aggregate | | components/src/renderers/basic/record-picker.tsx | element:record_picker options | | plugin-detail/src/renderers/record-activity.tsx | record:activity self-fetch | | plugin-detail/src/renderers/record-history.tsx | record:history self-fetch | | plugin-view/src/ObjectView.tsx | non-grid (kanban / calendar / gallery / timeline) fetch |

.changeset/6771-retire-body-child-list-dialect.md

  • names renderers/layout/containers.tsx → packages/components/src/renderers/layout/containers.tsx — edited by this change

    Non-rendering readers keep their arm on the same rule, and none of them renders anything: while a renderer still reaches stored body content, a reader that must see the SAME content keeps its arm, or the renderer draws what the reader cannot find. Those are the two tab-subtree walkers in renderers/layout/containers.tsx, app-shell's pageSchemaIntrospect (CONTAINER_KEYS) and PageBlockInspector (STRUCTURAL_PROP_KEYS, the inspector half of a stored properties.body), and the CLI's OBJECTUI_STRUCTURAL_KEYS — a file-IDENTIFICATION marker, where keeping body is what lets an old file still be recognised as an ObjectUI node and therefore refused, instead of silently not judged.

.changeset/6881-retire-data-table-toolbar.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    What was measured. The key was declared on both published faces — data-display.ts (toolbar?: SchemaNode[], "Table toolbar actions/content") and the Zod mirror (SchemaNode | SchemaNode[]) — documented, mirrored, and read by NOTHING: data-table.tsx, the registered renderer for type: 'data-table', contains the word only in two prose comments and never reads schema.toolbar. The sibling emptyAction slot on the same interface IS mounted through SchemaRenderer, so the census zero is a reading, not a blind query. An author who wrote a toolbar got a green document and a blank result, with no signal anywhere that said so — the declared-vs-enforced failure mode that is worst for AI-authored metadata, which has nothing but the declaration to go on.

.changeset/6882-datatable-declare-render-cell-editor-and-cell-classname.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    data-table has read both keys on its production path all along — renderCellEditor through a (schema as any) cast, cellClassName by destructuring it into the class of its three utility cells (the selection checkbox, the row number, the row actions). Neither was declared, so authoring either one was unchecked: a misspelling produced no error and no widget, and no editor completion offered them. DataTableSchema now declares both, and the cast in data-table.tsx is gone rather than replaced.

.changeset/6909-fieldeditwidget-dom-pass-through.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    Also corrects a comment in @object-ui/components' data-table.tsx that this change falsifies. It justified the injected editor's document-level pointerdown listener partly with "FieldEditWidget forwards autoFocus and nothing else out of the DOM block, so a host handler could not reach the control through it even if one were passed" — no longer true. The listener is still load-bearing for the other half of that reason, which is untouched: the renderCellEditor context object has nowhere to put an onBlur in the first place. Comment only; no behaviour change in that package.

.changeset/7070-no-invented-gantt-date-fields.md

  • names app-shell/src/views/ObjectView.tsx → packages/app-shell/src/views/ObjectView.tsx — edited by this change

    • app-shell/src/views/ObjectView.tsx — the console object page. The inline branch becomes ganttViewOptions, the sibling of calendarViewOptions and timelineViewOptions: the declared block spread whole, title floored at 'name', no date field invented. - plugin-list/src/ListView.tsx — the render branch AND the capability gate. - plugin-view/src/ObjectView.tsx — generateViewSchema, the authored object-view element route, which bypasses ListView entirely.
  • names plugin-list/src/ListView.tsx → packages/plugin-list/src/ListView.tsx — edited by this change

    • app-shell/src/views/ObjectView.tsx — the console object page. The inline branch becomes ganttViewOptions, the sibling of calendarViewOptions and timelineViewOptions: the declared block spread whole, title floored at 'name', no date field invented. - plugin-list/src/ListView.tsx — the render branch AND the capability gate. - plugin-view/src/ObjectView.tsx — generateViewSchema, the authored object-view element route, which bypasses ListView entirely.
  • names plugin-view/src/ObjectView.tsx → packages/plugin-view/src/ObjectView.tsx — edited by this change

    • app-shell/src/views/ObjectView.tsx — the console object page. The inline branch becomes ganttViewOptions, the sibling of calendarViewOptions and timelineViewOptions: the declared block spread whole, title floored at 'name', no date field invented. - plugin-list/src/ListView.tsx — the render branch AND the capability gate. - plugin-view/src/ObjectView.tsx — generateViewSchema, the authored object-view element route, which bypasses ListView entirely.

.changeset/7122-capability-manage-org-presentation.md

  • names useFieldTranslation.ts → packages/fields/src/widgets/useFieldTranslation.ts — edited by this change

    The label is the spec artifact's own (Manage Organization Presentation), read off the installed build rather than invented, and it is authored everywhere the widget's docblock requires of any edit to that list: useFieldTranslation.ts and all ten locale packs. Each non-English string is composed from that pack's own established sibling vocabulary (manage_org_users, manage_platform_settings) rather than machine-translated; a native review pass is welcome on the nine, and nothing about the capability's behaviour depends on the wording.

.changeset/7182-declared-action-ids-one-rule.md

  • names containers.tsx → packages/components/src/renderers/layout/containers.tsx — edited by this change

    New on @object-ui/types, beside actionRendersAt: the pure resolveDeclaredActionIds(elements, registeredActions), with the DeclaredActionsResolution / DeclaredActionsRefusal result types (the shape classifier stays module-internal: called with no registry, the function already returns the registry-independent verdict a renderer needs before its lookup). Both renderers call it; the whole-array switch in record-quick-actions.tsx and the per-element normalisation in containers.tsx are gone. The rule is closed: a string is an id, a non-null non-array object is an inline definition, and any other element (null, a number, a nested array) is refused at its index too. An all-id array resolves by name in authored order, first registration winning on a duplicate name; ids that name nothing are reported back with their index for the caller to warn about once its lookup has settled.

.changeset/7499-gantt-non-axis-floors-omitted.md

  • names plugin-list/src/ListView.tsx → packages/plugin-list/src/ListView.tsx — edited by this change

    • plugin-list/src/ListView.tsx — the object-gantt render branch. - plugin-view/src/ObjectView.tsx — generateViewSchema, the authored object-view element route, which bypasses ListView entirely.
  • names plugin-view/src/ObjectView.tsx → packages/plugin-view/src/ObjectView.tsx — edited by this change

    • plugin-list/src/ListView.tsx — the object-gantt render branch. - plugin-view/src/ObjectView.tsx — generateViewSchema, the authored object-view element route, which bypasses ListView entirely.

.changeset/7773-kanban-adapter-groupfield-write.md

  • names ListView.tsx → packages/plugin-list/src/ListView.tsx — edited by this change

    Who is NOT affected — the boundary is node-local. Every VIEW-LEVEL groupField read is untouched and still live: it is a legacy alias of the spec's groupByField on the kanban view config, mapped by normalize-list-view.ts, and both adapters still resolve lanes through it (ObjectView.tsx's kanbanCfg.groupField ||, ListView.tsx's groupByField || groupField). Authoring options.kanban.groupField on a list-view or object-view keeps working exactly as documented in packages/plugin-list/README.md. groupField is dead only on the generated object-kanban NODE.

.changeset/7780-object-kanban-record-source.md

.changeset/7804-data-table-handler-key-arms.md

  • names renderers/complex/data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    BaseSchema is .passthrough(), so a key no arm declares is not refused — it stops being judged and the value is KEPT. All seven were in that state while renderers/complex/data-table.tsx read and INVOKED each one, so an authored { "type": "data-table", "onRowClick": { "action": "toast" } } parsed GREEN and that action object was handed to a call site expecting a function. Each key is now a named refusal on the mirror (handlerKeyRefusal, the objectui#6124 shape), and the arm's message says why JSON cannot author it and what to write instead.

.changeset/8155-app-root-residue-swept.md

  • names containers.tsx → packages/components/src/renderers/layout/containers.tsx — edited by this change

    Swept as a class, not as two coordinates. Every other place in this tree that stated app was a bound expression-scope root is corrected in the same change — the diagnostic copy and its byte-pin, the ambient-scope docblocks in @object-ui/react (SchemaRenderer, useExpression), @object-ui/core (ActionRunner.ParamDef.visible, RowPredicateOptions.scope), @object-ui/components (form.tsx, containers.tsx), @object-ui/plugin-detail, @object-ui/plugin-form (docblock and README), @object-ui/app-shell and the console app, plus fourteen test fixtures that transcribed the old bag with an app key. The fixtures in @object-ui/app-shell now call buildExpressionScope instead of transcribing it, so that pair cannot drift again.

.changeset/8632-malformed-picklist-option-loud.md

  • names packages/i18n/README.md → packages/i18n/README.md — edited by this change

    Two new strings land in the designer's own en / zh tables — the metadata-admin console owns its strings in views/metadata-admin/i18n.ts and is deliberately outside the ten locale packs (packages/i18n/README.md, "Scope — the engine.* carve-out").

.changeset/8653-listview-title-retired-rowactiondefs-pinned.md

  • names packages/plugin-view/src/ObjectView.tsx → packages/plugin-view/src/ObjectView.tsx — edited by this change

    title — retired. ListView resolved its export filename through schema.label || (schema as any).title. @objectstack/spec/ui's ListViewSchema refuses title by name (unrecognized_keys: ['title']) while ObjectGridPropsSchema accepts it; packages/types mirrors the platform contract rather than ruling over it, so declaring title on ListViewSchema would have made this repo accept what the platform save gate rejects. That asymmetry is also why objectui#6639 could take the declare branch for ObjectGridSchema.title one package over and this site could not. A parse-based census of apps/ examples/ content/ and packages/ found zero list-view nodes authoring title, so the retirement costs no author a filename. Over that same corpus the instrument reports three object-grid nodes carrying the key: two authored ones, both in content/docs/api/schema-reference.md, plus one that is not authored at all — packages/plugin-view/src/ObjectView.tsx composes title: schema.table?.title onto a grid node it builds, so it is a producer writing the key rather than an author declaring it. ObjectGrid's own title reads are untouched — they remain declared, ruled and read.

.changeset/8990-object-kanban-groupby-optional.md

  • names packages/plugin-list/src/ListView.tsx → packages/plugin-list/src/ListView.tsx — edited by this change

    • packages/plugin-list/src/ListView.tsx generates the node as groupBy: laneField. objectDef loads asynchronously, so laneField is undefined on every load until it lands, and stays undefined whenever the object offers no stageField hint and none of status / stage / state / phase. The renderer serves that node; both published faces refused it. - content/docs/utilities/data-objectstack.mdx documents an object-kanban node that is exactly { type, dataSource }, with no groupBy. ⚠️ This one is weaker and is cited for what it is: that fragment is still refused after this change, at RECORD_SOURCE_REQUIRED, because dataSource is not a rung of the record-source ladder. It shows a lane-less board is a documented authoring; it is not a document this change admits.

.changeset/9174-interpolate-fastpath-trim.md

  • names packages/components/src/renderers/layout/containers.tsx → packages/components/src/renderers/layout/containers.tsx — edited by this change

    At this change page:header's title/subtitle (and the record-title titleFormat) all went through interpolate() in packages/components/src/renderers/layout/containers.tsx. When the template contained a {token} the function collapsed and trimmed whitespace before returning; when it contained no { at all it returned the raw string untouched. A whitespace-only authored title (e.g. ' ') has no token, so it came back unchanged — truthy — and PageHeaderRenderer's {explicitTitle && ANGLE-BRACKETS(h1)} gate drew a blank h1. Because literalTitleText (page.tsx), which decides whether PageRenderer delegates its own heading to the authored header, already trimmed and correctly read "no title", PageRenderer also drew its own implicit heading — two ANGLE-BRACKETS(h1) elements on one document, the broken outline objectui#3434 closed, arriving through a different door.

.changeset/9615-locale-count-ten.md

  • names packages/i18n/README.md → packages/i18n/README.md — edited by this change

    Correcting the numeral to ten was weighed and refused. A restated count is the construct that permitted the error, and this repository has now watched it recur three times over the same fact — objectui#3351 in a changeset file, objectui#7989 in packages/i18n/README.md, and this one in published source. So the number is gone rather than corrected: the sentence now claims only what it needs (the key is translated in every locale the package ships) and names all-locales-key-parity.test.ts as the instrument that re-derives it. That test asserts the stronger invariant — every pack defines every en key — over whatever the pack set is on the day it runs, so an eleventh pack makes the sentence more true rather than false, and there is no figure left to rot (AGENTS.md commandment 完善设计器的每一个细节 #9).

  • names all-locales-key-parity.test.ts → packages/i18n/src/__tests__/all-locales-key-parity.test.ts — edited by this change

    Correcting the numeral to ten was weighed and refused. A restated count is the construct that permitted the error, and this repository has now watched it recur three times over the same fact — objectui#3351 in a changeset file, objectui#7989 in packages/i18n/README.md, and this one in published source. So the number is gone rather than corrected: the sentence now claims only what it needs (the key is translated in every locale the package ships) and names all-locales-key-parity.test.ts as the instrument that re-derives it. That test asserts the stronger invariant — every pack defines every en key — over whatever the pack set is on the day it runs, so an eleventh pack makes the sentence more true rather than false, and there is no figure left to rot (AGENTS.md commandment 完善设计器的每一个细节 #9).

.changeset/9786-detail-timestamps-display-locale.md

  • names machineLocaleCensus-9909.test.ts → packages/i18n/src/__tests__/machineLocaleCensus-9909.test.ts — edited by this change

    Two instruments land with the repair, because the repair alone is what happened the last five times. detailTimestamps.displayLocale-9786.test.tsx renders every surface whose face reaches the DOM under a declared de-DE session and a declared en one and requires the two readings to differ — an assertion the runner's own locale cannot satisfy, since before this change both legs came from the machine and were byte-identical whatever the machine was. It also patches the Intl constructors and Date.prototype.toLocale* and inspects the argument each call actually receives, which is the only way to catch a variable that resolves to undefined, and the only way to reach RecordMetaFooter — whose absolute face is a TooltipContent child that Radix keeps out of the DOM until the tooltip opens. A census pin reddens when a new bare, undefined- or hard-coded-tag call site is written; it masks comments before it counts, and when it landed it declared InlineFieldInput's en-CA as an exemption with its reason — that call fed ANGLE-BRACKETS(input type="date"), whose value HTML defines as YYYY-MM-DD, so it was an ISO formatter and not a display locale. It landed scoped to this package and, under the ruling on this card, became the one repository-wide census in @object-ui/i18n (machineLocaleCensus-9909.test.ts, objectui#9909), which carried that exemption forward. objectui#10625 later removed the call: InlineFieldInput's date editor now renders @object-ui/fields' DateField, and the exemption went with it.

.changeset/9909-machine-locale-sweep.md

  • names machineLocaleCensus-9909.test.ts → packages/i18n/src/__tests__/machineLocaleCensus-9909.test.ts — edited by this change

    One census, repository-wide. plugin-detail's machine-locale census pin (objectui#9786) is now a single test, machineLocaleCensus-9909.test.ts in @object-ui/i18n, covering every workspace package whose manifest depends on @object-ui/i18n. It refuses any call site that passes nothing, undefined, the 'default' pseudo-tag (a subtag no locale data answers, so Intl resolves it to the machine's locale) or a hard-coded tag, unless the site is declared with its reason — for example the catch fallback for a tag Intl itself rejected, or an ISO formatter feeding ANGLE-BRACKETS(input type="date"). Each declared site is counted exactly, so deleting one without its entry is refused too. The per-package pin it replaces is deleted. The runtime tripwire that observes the argument each locale-taking call (Intl constructors, Date and Number toLocale*) actually receives moved into the private @object-ui/test-support package, and every surface above is pinned with it: the same data under two declared locales must render differently, NO locale-taking call may receive the machine's locale (nothing, undefined or 'default'), and the surface's own calls must receive the declared tag. A call carrying another declared tag, such as the 'en' of the session's UI language, is tolerated by design: only the machine's locale is the defect.

.changeset/dashboard-stable-empty-rows-4629.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    Nothing rendered wrong before or after; this is wasted work in the empty window, plus the live react-hooks/exhaustive-deps warning the conditional raised. It is the same module-scope frozen empty data-table.tsx adopted for its own EMPTY_ROWS (objectui#4618), applied to the provider: 'object' siblings.

.changeset/list-user-actions-collision-5398.md

  • names ListView.tsx → packages/plugin-list/src/ListView.tsx — edited by this change

    The harvest now reads the object block only. Both userActions read sites in ListView.tsx carry a comment naming the collision, and __tests__/ListView.userActionsCollision.test.tsx pins each clause of it: the two shapes, a producer that manufactures the view one, the harvest's blindness to it, and the projection that must keep the object's operand with a toolbar block — or an empty block — present on the view.

.changeset/listview-comment-pair-4559.md

  • names ListView.tsx → packages/plugin-list/src/ListView.tsx — edited by this change

    Two comment corrections in ListView.tsx (objectui#4559, objectui#4966). No runtime behaviour changes and the emitted bundle is byte-identical; the published .d.ts does change, which is why this is a patch rather than an empty frontmatter.

.changeset/object-view-unmirrored-keys-7779.md

  • names packages/plugin-view/src/ObjectView.tsx → packages/plugin-view/src/ObjectView.tsx — edited by this change

    What was measured. Every reading was taken on the object-view node renderer (packages/plugin-view/src/ObjectView.tsx, registered by plugin-view/src/index.tsx) with schema.objectName / schema.layout as the positive controls of the same schema.KEY query, so each zero is a reading; the repo-wide census of viewTabBar finds the key in no source file outside @object-ui/types (two doc tables listed it as authorable and are corrected here). The spec side was read through the installed pin (@objectstack/spec@17.2.0, ui entry, 117 exported object schemas walked; control keys objectName / columns / navigation / listViews hit): the three spec-modelled keys are optional slots on ListViewSchema and ObjectListViewSchema; the six local keys have no spec slot anywhere.

.changeset/record-picker-label-placeholder-i18n-5637.md

  • names renderers/layout/containers.tsx → packages/components/src/renderers/layout/containers.tsx — edited by this change

    KNOWN GAP, unchanged by this release: the sibling label read sites in renderers/layout/containers.tsx compose translateLabel(pickLocalized(…), language), and that second helper is not applied here — translateLabel and its KNOWN_LABEL_DICT are module-private to that file. Only the locale-map resolution lands in this change; a plain-English string label is still rendered verbatim in every language, exactly as before.

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Angle-bracketed names in the quoted prose above are rewritten as ANGLE-BRACKETS(name): GitHub deletes tag-shaped fragments from a stored body, and a quote that silently loses the identifier it is about is worse than a visible repair.

Compared the checked-out tree with 0e9058b95 (merge-base with origin/main): 55 file(s) changed outside .changeset/, read against 1991 pending declaration(s) that publish a body (2619 pending in total). · run

@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3552.3 KB 3574.6 KB
Main entry chunk (gzip) 150.4 KB 350 KB
Entry file index-XmwiNHc6.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 (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.88KB 6.25KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
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.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 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) 570.76KB 136.75KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 230.46KB 63.94KB
fields (index.js) 261.57KB 66.16KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.49KB 9.23KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 40.40KB 11.41KB
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.99KB 0.87KB
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) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 53.17KB 15.46KB
plugin-charts (index.js) 83.93KB 22.90KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 141.26KB 38.01KB
plugin-designer (index.js) 224.00KB 46.96KB
plugin-detail (index.js) 245.27KB 64.47KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 172.73KB 44.50KB
plugin-gantt (index.js) 179.14KB 45.06KB
plugin-grid (index.js) 232.66KB 63.77KB
plugin-kanban (index.js) 49.51KB 15.54KB
plugin-list (index.js) 116.60KB 28.96KB
plugin-map (index.js) 25.60KB 8.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 38.80KB 11.71KB
plugin-tree (index.js) 14.51KB 5.15KB
plugin-view (index.js) 90.23KB 22.73KB
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.81KB 3.58KB
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) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.06KB 2.68KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 21.42KB 7.05KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 3.19KB 1.62KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.07KB 2.39KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
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 (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 21.59KB 7.71KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
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

…amilies supersede

The pending entries for objectui#9664, objectui#10024, objectui#10242,
objectui#10425 and objectui#10636 describe a `…One` / `…Plural` switch and a
ru / ar count label on its plural half. This release replaced each switch with
an i18next count family, so each entry gains a pure-append dated note naming the
family that replaced it; frontmatter and existing body are untouched.

The objectui#11445 note's Supersedes paragraph now points at those dated notes
and says the count labels stay only on the base key.

Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3552.3 KB 3574.6 KB
Main entry chunk (gzip) 150.4 KB 350 KB
Entry file index-XmwiNHc6.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 (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.88KB 6.25KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
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.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 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) 570.76KB 136.75KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 230.46KB 63.94KB
fields (index.js) 261.57KB 66.16KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.49KB 9.23KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 40.40KB 11.41KB
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.99KB 0.87KB
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) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 53.17KB 15.46KB
plugin-charts (index.js) 83.93KB 22.90KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 141.26KB 38.01KB
plugin-designer (index.js) 224.00KB 46.96KB
plugin-detail (index.js) 245.27KB 64.47KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 172.73KB 44.50KB
plugin-gantt (index.js) 179.14KB 45.06KB
plugin-grid (index.js) 232.66KB 63.77KB
plugin-kanban (index.js) 49.51KB 15.54KB
plugin-list (index.js) 116.60KB 28.96KB
plugin-map (index.js) 25.60KB 8.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 38.80KB 11.71KB
plugin-tree (index.js) 14.51KB 5.15KB
plugin-view (index.js) 90.23KB 22.73KB
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.81KB 3.58KB
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) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.06KB 2.68KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 21.42KB 7.05KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 3.19KB 1.62KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.07KB 2.39KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
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 (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 21.59KB 7.71KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
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

`Doc Snippet Type Check` compiles every ts/tsx block in a covered README in
isolation against the built types. The new "Count labels are i18next count
families" block was a bare call (`t(...)` with `replies` undeclared), so it
failed with TS2304 on both names. It now declares both the way its
`useObjectTranslation` sibling does: an import, a component taking `replies`,
and `t` from the hook. The rendered forms in the comment are the en and ru
pack values for `detail.replyCount`.

Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3552.3 KB 3574.6 KB
Main entry chunk (gzip) 150.4 KB 350 KB
Entry file index-XmwiNHc6.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 (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.88KB 6.25KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
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.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 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) 570.76KB 136.75KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 230.46KB 63.94KB
fields (index.js) 261.57KB 66.16KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.49KB 9.23KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 40.40KB 11.41KB
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.99KB 0.87KB
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) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 53.17KB 15.46KB
plugin-charts (index.js) 83.93KB 22.90KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 141.26KB 38.01KB
plugin-designer (index.js) 224.00KB 46.96KB
plugin-detail (index.js) 245.27KB 64.47KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 172.73KB 44.50KB
plugin-gantt (index.js) 179.14KB 45.06KB
plugin-grid (index.js) 232.66KB 63.77KB
plugin-kanban (index.js) 49.51KB 15.54KB
plugin-list (index.js) 116.60KB 28.96KB
plugin-map (index.js) 25.60KB 8.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 38.80KB 11.71KB
plugin-tree (index.js) 14.51KB 5.15KB
plugin-view (index.js) 90.23KB 22.73KB
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.81KB 3.58KB
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) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.06KB 2.68KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 21.42KB 7.05KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 3.19KB 1.62KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.07KB 2.39KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
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 (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 21.59KB 7.71KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
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

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 2, 2026 11:11
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 2, 2026 11:11
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 2, 2026
Merged via the queue into main with commit 4a1adb7 Oct 2, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-11445-count-families branch October 2, 2026 11:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants