Skip to content

fix(plugin-grid,components,fields,plugin-detail,plugin-gantt,console): HotCRM rendering — column label, widget fallback and rating cell, reference rail, gantt year, approval drawer, public form heading (objectui#12110) - #12151

Merged
objectstack-fleet[bot] merged 12 commits into
mainfrom
claude/issue-12110-hotcrm-rendering
Oct 11, 2026

Conversation

@objectstack-fleet

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

Copy link
Copy Markdown
Contributor

Part of #12110 (items 1 to 6, and the heading half of item 7)
Clause-②: no

Draft by dispatch: the domain:ui seat 2 PM (session session_01TYgwmFK1q4KJ6Qq2WRLzsD) dispatched this run on claim comment 6108276574. The claim's branch is this branch. The seat moves this PR; the dev does not mark it ready.

What stays open on the card: the second half of item 7, "Hide editor hints on the public surface". It needs a decision the card does not make (see "Stopped" below), so this PR names the card with Part of and does not close it.

The items

Each item has its own commit and its own pin. Each pin was run against the BASE source (c9fc10b1ec) first, and every defect leg failed there for the reason the card states, while its controls passed (the readings are under "Measured red on base").

1. An authored column label wins over the field label (A-02). ObjectGrid built col.label || field.label || prettified name and handed that to the translated field-label lookup as its FALLBACK, so a bundle translating the field replaced the override. The spec calls ListColumnSchema.label the "Display label override". An authored label is now the header as written; a column without one keeps the translated field label. Pin: ObjectGrid.authoredColumnLabel-12110 (en and zh, plus the no-override control).

2. An unregistered widget degrades to the type renderer; rating cells draw stars (A-03).

  • Contract check first: the spec text the card cites is FieldSchema.widget's own description, "names a registered field component (resolved as field:WIDGET) … Degrades to the type renderer when unregistered", read in the installed @objectstack/spec 17.7.0 and again on objectstack main (b7cd1af9df). star_rating is a free string: the spec declares no such widget, so the fix is the fallback, not a registration. There is no alias: each hint is taken or not, by one rule.
  • The form renderer (form.tsx) now takes a widget hint (form config first, then the field metadata) only when it can render: a registered field widget (resolved exactly as renderFieldComponent resolves it), a built-in control (input, textarea, checkbox, switch, select), or one of the default arm's own native email / password inputs and the secret refusal. The last group is kept on purpose: degrading a secret-declaring hint to its field's type could put the value on screen as clear text, the one degrade objectui#5322 rules out. Anything else falls back to type. The cascade-clear effect, which reads "which control is this row", now reads the same resolution, so the two readers cannot disagree.
  • rating cells: the standard cell table mapped rating to NumberCellRenderer. A new module-private RatingCellRenderer draws the star row RatingField's read-only branch draws, out of the field's max (5 when none is declared, Field.rating's own default), with the value and the scale as accessible text. It is reached through getCellRenderer('rating'), so the package's export surface does not grow.
  • Pins: form-unregistered-widget-type-fallback-12110 (the renderer, with probes), objectFormUnregisteredWidget-12110 (the real door: ObjectForm with the fields package registered), cellRenderers.ratingCell-12110.
  • Two existing plugin-form tests (objectFormNumericStep-9574, sectionEntryOverrides-10475) reached the old fallback through widget: 'nothing-registers-this' to see the producer's step spread onto an input. That route is exactly what the spec rules out. They now reach the same spread through the built-in input control (widget: 'input'), and ObjectForm's comment on that route says so.

3. The reference rail names a row by the target's nameField (B-13). The same schema read that served fields now keeps the object's declared name field (declaredNameField from @object-ui/core), and pickDisplayName reads it through recordDisplayValueAt before the fixed ladder: the same two halves getRecordDisplayName reads its declared rung with.

4. refreshAfter: true refreshes the reference rail (B-14). The rail now subscribes to the data-invalidation bus (subscribeDataChanges + dataChangeMatches from @object-ui/react). A change to the parent record (what RecordDetailView's notifyRecordChanged posts after a refreshAfter: true action), to an entry's own object, or to everything bumps a nonce the fetch signature carries. A refetch of the scope already on screen keeps its rows and counts up while it reads: no skeleton, no remount (AGENTS.md #8). The pin checks the row's DOM node survives the refresh. RecordDetailView.tsx is not touched: its existing post is what the rail now hears.

Pin for 3 and 4: record-reference-rail.nameFieldRefresh-12110. The item 4 legs read through the unchanged ladder, so they stand on their own.

5. Gantt dates name their year (B-20). The six faces the card lists (Start / End cells, the row date line, the bar tooltip, the summary drag chip, the bar's drag overlay) now go through one helper. It adds the year when the date's year is not the current one ("now" in the view's zone, as the today line reads it): two digits on the compact numeric faces, because the Start / End cells are narrow, and in full on the short-month tooltip. Existing pins with 2020 and 2024 fixtures now read the year. objectGanttInputs-11168 uses 2026 fixtures, so its date legs are pinned to a 2026 clock (Date only); otherwise they would change meaning on New Year's Day. Pin: GanttView.dateYear-12110 (clock pinned).

Round 1 (be73faf): examples/schema-catalog's objectql-record-source-render-identity-6939 freezes the clock in 2026 over three 2023/2024 catalog plans, so item 5 moved its text. Its PRE_REPAIR visibleText and sha256 were re-measured through the file's own measure(): with the year segments removed the text equals the old reading, and elements / tags are unchanged. Against the pre-item-5 GanttView.tsx the new literal fails at visibleText on all three tiles; at be73faf it passes.

6. Approval drawer: the approver and the headline (C-18).

  • Approver: a position:NAME approver the server left unresolved read positi…ager. It now reads the position's label from the console's metadata store: useMetadata().ensureType('position'), the position registry (ADR-0131 D3), the same read Setup's sharing-rule recipient picker makes since objectui#7611 / feat(app-shell,fields,console): Setup's positions and permission sets read the registry, through the metadata-admin pages' environment scope (part of objectui#7611) #12089. The store fetches it once per session and shares it. That is the source used; no new fetch path. The registry is asked only when a position approver is unresolved, and the prettified machine name stands in when the registry has no such item. The raw reference stays in the chip's title.
  • Headline: decisionAmountEntry led with the first key whose NAME matched an amount pattern, zero or not. HotCRM's account declares annual_revenue as currency and child_account_revenue as a summary roll-up (read in hotcrm's account.object.ts), and the drawer led with "Child Account Revenue 0". A field the object declares as currency now leads (its first non-zero value, else its zero). A name match leads only with a non-zero value.
  • The summaryFaces-11677 lead-label leg used the dogfood invoice, whose total: 0 is a summary roll-up, so it is exactly the zero this item keeps out of the headline. That leg now reads a billed invoice (total: 1180); its purpose (the lead takes the metadata label) is unchanged.
  • Raw option values are not in this item: objectui#11677 already draws them through RecordFieldFace, which this PR does not touch, and neither payloadSummary.
  • Pin: ApprovalsInboxPage.approverHeadline-12110.

7 (heading half). The public form is headed by its object's label (A-09). loadPublicForm fell back from the form's title straight to the API name. The object's label, served on the payload's objectSchema, now comes before it, and the API name stays the last resort. The objectui#8408 no-widening control pinned the API name in exactly this case; it moves one arm down (no object label either: API name), and the no-title leg now pins the object's label.

Stopped: the editor hints on the public surface (item 7, second half)

Measured on this branch's base: a markdown field on the anonymous /f/SLUG page shows "Format: markdown" (a throwaway render test of the real FormPage in public mode, deleted after the reading). The hints come from RichTextField's editable branch, which renders them on every host.

RichTextField cannot tell it is on the public form. FormPage knows its mode, but nothing passes it to a widget: the widget contract (FieldWidgetComponentProps) has no such member, and no context carries it. Hiding the hints only there needs a new contract member or a new export, and the card does not make that call. The options and a recommendation are in the dispatch report on the card.

Measured red on base

One run of every pin against the base source, before any fix:

  • item 1: headers ["#","Account Name","Industry"] (en) and ["#","客户名称","行业"] (zh), where the override "Company" belongs;
  • item 2: no rating-probe (the text box rendered); ObjectForm: zero stars; the cell: zero stars;
  • item 3: CS-0001 not found (the row read its subject);
  • item 4: the rail's base source written over the fixed one (fix committed first, restored from HEAD afterwards, blob hash checked equal to HEAD): both item 4 legs red (the new note never appeared after the bus post), the item 3 leg red, the two controls green;
  • item 5: 3/2 where 3/2/25 belongs; the tooltip Mar 2 → Mar 6;
  • item 6: chip text positi…ager; lead child_account_revenue;
  • item 7: heading ats_inquiry where Inquiry belongs.

Tests and gates

The branch merged main at 158c75d (objectui#12140 and #12142; no file of this branch is touched by either). Head: fd4059c.

After the merge, at fd4059c:

  • turbo run type-check for plugin-grid, components, fields, plugin-form, plugin-detail and plugin-gantt (green; the console's task was cut by the foreground cap behind the dependency rebuild), then @object-ui/console alone: 36 of 36 tasks green. Each package's tsconfig.test.json includes src/**/*.test.tsx by glob (not counted with --listFiles).
  • The 12110 pins, main's neighbours (pagerButtonNames-12124, affordanceGrantMap-12082) and the column-identity ratchet: 9 files, 82 tests passed.
  • packages/plugin-detail/ (4 shards): 255 files passed, 1 skipped file. packages/components/ (6 shards): 375 passed, 1 skipped file. scripts/__tests__/ (3 shards): 180 passed, 2 skipped files.
  • node scripts/check-changeset-presence.mjs, check:changeset-claims, check:pending-changeset-literals, check:new-line-citations, check:control-bytes, check:test-path-roots, the three check:vi-mock-*, check:phantom-deps, check:doc-json, check:doc-fences: all exit 0.

Before the merge, at 50c7747 (packages the merge did not change under this branch's files): packages/plugin-grid/ 202 files passed; packages/plugin-gantt/ 99 passed, 1 skipped file; packages/fields/ 245 passed, 1 skipped file; packages/plugin-form/ 175 passed; apps/console/ 181 passed.

Lint: objectui's package lint is eslint . with inline config honoured. This branch ran eslint on its 22 changed TypeScript files only: 0 errors. On changed lines the warnings are: pre-existing signatures (any parameters kept as they were), one pre-existing exhaustive-deps on the rail's fetch effect, and four new no-explicit-any warnings in two new test files (a probe's props, and the as any casts the neighbouring ObjectForm tests use); ApprovalsInboxPage.tsx reads 19 warnings at head and 19 at base. The config enables no type-aware linting, so this diff cannot move a verdict on an unchanged file; the full eslint . run is CI's.

Eager closure: NOT MEASURED as the console's eager closure (that needs two console builds). Proxy, base against head, the packages' built dist/index.js: fields +776 bytes raw, +170 gzip; components +218 raw, +79 gzip. The Star icon itself comes from lucide-react, outside the package dist; RatingField already imported it, lazily.

Acceptance notes (observations, not filed)

  • ObjectForm's numeric formField.step reaches the DOM now only through a widget: 'input' hint (or a host with no field widgets registered): the registered number, currency and percent widgets derive their own step, and an unregistered hint no longer reaches the old fallback. A dormant producer, not a defect. Carrier: none.
  • The Gantt's shift-mode header labels each shift-day { month: 'short', day: 'numeric' }, and shift mode has no year tier above it. Same family as item 5, not measured at a public door. Carrier: none.
  • Two readers name an approver position: the record page's approval panel reads sys_position rows (useApproverDirectory, the spec's APPROVER_VALUE_BINDINGS source), and the inbox now reads the position registry. A registry-only position reads as its prettified name on the record page and as its label here. Not measured as a defect. Carrier: whoever next touches approverIdentity.ts.
  • The claim's file surface was exceeded in three places, each forced by an item: plugin-form's ObjectForm.tsx (a comment the item 2 fix made false) and two plugin-form tests that relied on the retired route; ApprovalsInboxPage.tsx beyond formatIdentity / decisionAmountEntry (a small usePositionLabels hook, and the label map threaded through approverDisplay / approverChips); form.tsx's cascade-clear reader. Docs touched (pre-authorised): plugin-grid.mdx, plugin-form.mdx, plugin-gantt.mdx, guide/public-forms.md.

Generated by Claude Code

claude added 11 commits October 11, 2026 11:22
…(objectui#12110 item 1)

`ListColumnSchema.label` is the spec's "Display label override", but the
grid handed it to the translated field-label lookup as that lookup's
fallback, so any bundle translating the field replaced the override. The
authored label is now the header as written; the field-label lookup runs
only for a column with no override.

Pin: ObjectGrid.authoredColumnLabel-12110 (en and zh, plus the no-override
control).

Claude-Session: https://claude.ai/code/session_01TYgwmFK1q4KJ6Qq2WRLzsD
Co-authored-by: Claude <noreply@anthropic.com>
… (objectui#12110 item 1)

Claude-Session: https://claude.ai/code/session_01TYgwmFK1q4KJ6Qq2WRLzsD
Co-authored-by: Claude <noreply@anthropic.com>
…enderer, and a rating cell draws stars (objectui#12110 item 2)

The spec's FieldSchema.widget promises an unregistered widget "Degrades
to the `type` renderer when unregistered". The form renderer took the
hint whether or not anything could draw it, so a `rating` field with
`widget: 'star_rating'` fell to the default text box. Each hint (form
config, then field metadata) is now taken only when it can render: a
registered field widget, a built-in control, or one of the default arm's
own native email/password inputs and the secret refusal (kept so a
secret-declaring hint never degrades to clear text). Otherwise `type`.
The cascade-clear effect reads the same resolution.

The `rating` cell drew a bare number; it now draws the star row
RatingField's read-only branch draws, out of the field's `max` (5 by
default). Module-private, reached through getCellRenderer('rating').

Two plugin-form tests reached the old fallback through a widget nothing
registers; they now reach the same props spread through the built-in
`input` control, and ObjectForm's comment on that route says so.

Pins: form-unregistered-widget-type-fallback-12110,
objectFormUnregisteredWidget-12110, cellRenderers.ratingCell-12110.

Claude-Session: https://claude.ai/code/session_01TYgwmFK1q4KJ6Qq2WRLzsD
Co-authored-by: Claude <noreply@anthropic.com>
…fetches on the invalidation bus (objectui#12110 items 3 and 4)

Item 3: the rail named each related row through a fixed ladder of key
names and kept only `fields` of the target schema. The same schema read
now keeps the object's declared name field (core's declaredNameField),
and a row is named by it, through recordDisplayValueAt, before the
ladder.

Item 4: the rail latched its fetch on (parentId + entries) and never
subscribed to the data-invalidation bus, so `refreshAfter: true` (which
posts notifyDataChanged for the parent record) left it stale. It now
subscribes, and a change to the parent record, to an entry's object or
to everything bumps a nonce the fetch signature carries. A refetch of the
scope already on screen keeps its rows up while it reads: refresh in
place, no skeleton, no remount (AGENTS.md #8). RecordDetailView is not
touched: its existing post is what the rail now hears.

Pin: record-reference-rail.nameFieldRefresh-12110.

Claude-Session: https://claude.ai/code/session_01TYgwmFK1q4KJ6Qq2WRLzsD
Co-authored-by: Claude <noreply@anthropic.com>
…rrent one (objectui#12110 item 5)

The Start / End cells, the row date line, the bar tooltip and the two drag
previews formatted { month, day } only. One helper, formatTaskDay, adds
the year when the date's year differs from the current one (the view
zone's `now`, as the today line reads it): two digits on the compact
numeric faces, in full on the short-month tooltip.

Existing pins with 2020 / 2024 fixtures now read the year; the 2026
fixture of objectGanttInputs-11168 is pinned to a 2026 clock (Date only)
so it cannot change meaning on New Year's Day.

Pin: GanttView.dateYear-12110 (clock pinned).

Claude-Session: https://claude.ai/code/session_01TYgwmFK1q4KJ6Qq2WRLzsD
Co-authored-by: Claude <noreply@anthropic.com>
…s with the declared amount (objectui#12110 item 6)

Approver: `formatIdentity` middle-truncated `position:sales_manager` to
`positi…ager`. A position reference now reads the position's label from
the console's metadata store (`ensureType('position')`, the registry
read Setup's recipient picker makes, ADR-0131 D3), else the prettified
name. Asked only for positions the server left unresolved.

Headline: `decisionAmountEntry` led with the first name-matched key, so
HotCRM's account led with "Child Account Revenue 0". A field declared
`currency` now leads (its first non-zero, else its zero), and a name
match leads only with a non-zero value. The 11677 lead-label pin moves to
a billed invoice: its dogfood invoice's `total: 0` is the zero roll-up
this change keeps out of the headline.

Pin: ApprovalsInboxPage.approverHeadline-12110.

Claude-Session: https://claude.ai/code/session_01TYgwmFK1q4KJ6Qq2WRLzsD
Co-authored-by: Claude <noreply@anthropic.com>
…abel (objectui#12110 item 7, heading half)

`loadPublicForm` fell back from the form's title straight to the object's
API name. The object's label, served on the payload's `objectSchema`,
now comes first; the API name stays the last resort.

The objectui#8408 no-widening control pinned the API name in exactly this
case; it moves one arm down (no object label either -> API name), and
the no-title leg now pins the object's label.

The other half of item 7 (editor hints on the public surface) is not in
this commit: no signal tells a field widget it renders on the public
form, and adding one is a contract change the card does not make.

Claude-Session: https://claude.ai/code/session_01TYgwmFK1q4KJ6Qq2WRLzsD
Co-authored-by: Claude <noreply@anthropic.com>
…e ref in an effect (objectui#12110 item 6)

The console type-check read the pin's hoisted stubs as `never[]` / `null`
answers; they now declare the row shape. `usePositionLabels` wrote its
store ref during render (react-hooks/refs); it now syncs it in an effect
declared ahead of the read.

Claude-Session: https://claude.ai/code/session_01TYgwmFK1q4KJ6Qq2WRLzsD
Co-authored-by: Claude <noreply@anthropic.com>
…ectui#12110 item 5)

ObjectGantt.catalogPlans-11141 derived the End cell's expected text as
MONTH/DAY from the authored date; the catalog's plans are dated 2024,
which is not the current year, so the cell now names the year too.

Claude-Session: https://claude.ai/code/session_01TYgwmFK1q4KJ6Qq2WRLzsD
Co-authored-by: Claude <noreply@anthropic.com>
Brings in objectui#12140 (RecordDetailView feed handlers, RelatedList,
record-related-list) and objectui#12142 (data-table pager names). No
file of this branch is touched by either; RecordDetailView's
`refreshAfter` post, which item 4's rail now hears, is unchanged.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 291 chunks) 3177.5 KB 3204.6 KB
Main entry chunk (gzip) 74.2 KB 350 KB
Entry file index-DYuGtS-G.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) 20.05KB 7.41KB
app-shell (runtime-config.js) 22.59KB 7.89KB
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.15KB 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) 41.19KB 11.12KB
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) 587.98KB 141.60KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 240.39KB 67.09KB
fields (index.js) 276.06KB 70.25KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
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.52KB 2.39KB
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) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 40.26KB 11.71KB
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) 15.35KB 5.51KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
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) 54.31KB 15.86KB
plugin-charts (index.js) 84.72KB 23.27KB
plugin-chatbot (index.js) 201.72KB 48.07KB
plugin-dashboard (index.js) 144.20KB 38.95KB
plugin-designer (index.js) 233.77KB 50.02KB
plugin-detail (index.js) 250.93KB 66.24KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 177.80KB 46.04KB
plugin-gantt (index.js) 178.63KB 45.19KB
plugin-grid (index.js) 255.20KB 70.94KB
plugin-kanban (index.js) 53.23KB 16.71KB
plugin-list (index.js) 122.82KB 31.24KB
plugin-map (index.js) 27.24KB 9.03KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 39.21KB 11.85KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 92.11KB 23.27KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
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.31KB 2.07KB
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.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
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) 23.87KB 7.83KB
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 (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
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) 1.13KB 0.65KB
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.78KB 2.70KB
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 (node-slots.js) 7.18KB 2.34KB
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) 19.93KB 7.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
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

… rule (objectui#12110 item 5)

objectql-record-source-render-identity-6939 freezes the clock in 2026 and
renders three 2023/2024 catalog plans, so item 5's year rule now writes
`2/1/24 → 2/14/24` where its PRE_REPAIR literal held `2/1 → 2/14`. The
three tiles' `visibleText` and `sha256` are re-measured through the
file's own measure() at fd4059c; with the year segments removed the
text equals the d88e20f reading exactly, and `elements` / `tags` are
unchanged. The assertion is not loosened and no reading is dropped.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 291 chunks) 3177.5 KB 3204.6 KB
Main entry chunk (gzip) 74.2 KB 350 KB
Entry file index-DYuGtS-G.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) 20.05KB 7.41KB
app-shell (runtime-config.js) 22.59KB 7.89KB
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.15KB 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) 41.19KB 11.12KB
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) 587.98KB 141.60KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 240.39KB 67.09KB
fields (index.js) 276.06KB 70.25KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
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.52KB 2.39KB
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) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 40.26KB 11.71KB
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) 15.35KB 5.51KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
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) 54.31KB 15.86KB
plugin-charts (index.js) 84.72KB 23.27KB
plugin-chatbot (index.js) 201.72KB 48.07KB
plugin-dashboard (index.js) 144.20KB 38.95KB
plugin-designer (index.js) 233.77KB 50.02KB
plugin-detail (index.js) 250.93KB 66.24KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 177.80KB 46.04KB
plugin-gantt (index.js) 178.63KB 45.19KB
plugin-grid (index.js) 255.20KB 70.94KB
plugin-kanban (index.js) 53.23KB 16.71KB
plugin-list (index.js) 122.82KB 31.24KB
plugin-map (index.js) 27.24KB 9.03KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 39.21KB 11.85KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 92.11KB 23.27KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
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.31KB 2.07KB
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.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
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) 23.87KB 7.83KB
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 (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
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) 1.13KB 0.65KB
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.78KB 2.70KB
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 (node-slots.js) 7.18KB 2.34KB
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) 19.93KB 7.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
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 11, 2026 19:47
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 11, 2026 19:48
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 11, 2026
Merged via the queue into main with commit 5330038 Oct 11, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-12110-hotcrm-rendering branch October 11, 2026 20:05
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.

2 participants