Skip to content

test(plugin-detail): pin the viewport in the #8350 dedupe pin, and cover the mobile branch - #8445

Merged
os-justin merged 1 commit into
mainfrom
claude/issue-8399-dedupe-pin-pins-viewport
Sep 8, 2026
Merged

test(plugin-detail): pin the viewport in the #8350 dedupe pin, and cover the mobile branch#8445
os-justin merged 1 commit into
mainfrom
claude/issue-8399-dedupe-pin-pins-viewport

Conversation

@os-justin

Copy link
Copy Markdown
Collaborator

Fixes #8399

record-details.dedupeEmptinessTrims-8350.test.tsx argued that a missing row could only be the dedupe's doing, because every fixture stayed below DetailSection's auto-hide minimum. Both halves of that argument are viewport-dependent, and the file pinned neither value. It was green because happy-dom reports innerWidth 1024.

The margin, recounted on this base (ca3942729), not taken from the card

Rendered rows after the dedupe, and how many of them are empty today (post-#8396), measured at a pinned 1280 by counting rows in the container and No value placeholders by title:

fixture rendered rows empty mobile auto-hide fires?
CONTRACT_FIELDS 2 1 no — 2 is below the mobile minimum 3
ACTIVITY_FIELDS 3 2 yes — 3 meets the minimum, 0.67 clears 0.2
PLAIN_FIELDS 2 1 no
NON-REGRESSION 2 0 no

The card said "3 rendered rows, 2 of them empty" for the ACTIVITY fixture and that is confirmed. Two things the card did not say, both of which come out of the recount:

  • the other three fixtures render 2 rows, not 3. The file's own docblock said "every fixture below renders 3 rows after the dedupe" — that was never true of three of the four, and it is now corrected to the measured numbers.
  • the thresholds re-read on this base are unchanged from the card: AUTO_HIDE_MIN_FIELDS = isMobile ? 3 : 4, AUTO_HIDE_RATIO = isMobile ? 0.2 : 0.25 (DetailSection.tsx:258-260).

The hazard is open — it is not a hypothetical, and it did not close

Ablation from the committed implementation, mutation proven on disk by git hash-object and restored by state (git diff HEAD empty and the hash back to git rev-parse HEAD:PATH), trap on EXIT INT TERM with absolute paths:

Leg 1 — pin the existing describe to the mobile branch (1280 to 375):

Tests  1 failed | 9 passed (10)
 × WHITESPACE AT THE DERIVATION RUNG — HALF 2: both blank rows survive
TestingLibraryElementError: Unable to find an element with the text: contract_no

Exactly the predicted failure, and note what the message says: nothing about auto-hide. That one line is the whole reason this card exists.

Leg 2 — pin the new mobile describe to desktop (375 to 1280):

Tests  2 failed | 8 passed (10)
 × MOBILE HALF 1 …
AssertionError: mobile auto-hide must fire on this fixture (3 rendered rows meets AUTO_HIDE_MIN_FIELDS 3, 2/3 empty clears AUTO_HIDE_RATIO 0.2) — this row is absent because it was HIDDEN, not because the dedupe took it
 × MOBILE HALF 2 …
AssertionError: without the reveal toggle there is nothing to reveal and this case measures nothing

The new cases are not vacuous, and the contrast between the two legs is itself the argument for reading presence through queryByText into expect(value, message).

What changed

  1. The viewport is pinned, and the branch is chosen out loud. DESKTOP_WIDTH for the existing describe, with a comment saying why: these cases assert which row the dedupe drops, and that reading is only clean while no second mechanism can remove a row — which at these fixture sizes is true only on the desktop branch. Pinning desktop is the decision, not the accident.
  2. The docblock is corrected. It claimed DetailSection's emptiness test "is raw — a whitespace-only value counts as FILLED there". fix(plugin-detail): DetailSection shares ONE definition of emptiness, and it trims #8396 made that definition trim, which is precisely what turned this file's margin from comfortable into zero. The stale sentence is replaced by the measured table above and by the statement that the file now pins the viewport.
  3. The mobile branch is covered as a SECOND describe, never a substitute. It does not re-assert the same rows; it pins the distinction the desktop choice rests on. A deduped row and an auto-hidden row are both "not on screen", and only one comes back: MOBILE HALF 1 shows both blank rows hidden with DetailSection offering Show 2 empty fields, MOBILE HALF 2 clicks that toggle, gets both rows back, and asserts that Acme Corporation — the row the dedupe removed — does not come back.

Checks

  • pnpm exec vitest run packages/plugin-detail/src/renderers/__tests__/record-details.dedupeEmptinessTrims-8350.test.tsxTest Files 1 passed (1) / Tests 10 passed (10) (was 8).
  • pnpm --filter @object-ui/plugin-detail type-check — exit 0. The test file is genuinely in that program: tsc -p tsconfig.test.json --listFiles lists it (1 hit, with DetailSection.tsx as the lit control).
  • pnpm exec eslint . in packages/plugin-detail — exit 0, 0 errors, 901 pre-existing warnings; the two on this file are the pre-existing no-explicit-any on renderBody.
  • Changeset — node scripts/check-changeset-presence.mjs verdict: 1 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/issue-8399-dedupe-pin-pins-viewport.md. Every one of them has an EMPTY frontmatter — declared as releasing nothing, which is the explicit exemption and a complete answer to this gate. check-changeset-no-major.mjs also exit 0.
  • No control bytes in either changed file (grep -naP for the control ranges, zero hits, with a lit control that fires on a file containing one).

Out of scope, filed separately

#8444 — forcing useIsMobile to answer "mobile" turns 21 of 137 test files in this package red (77 of 1233 tests). Same class, one layer out. Not touched here.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S


Generated by Claude Code

…ver the mobile branch (objectui#8399)

`record-details.dedupeEmptinessTrims-8350.test.tsx` kept every fixture below
`DetailSection`'s auto-hide minimum so that a missing row could only be the
dedupe's doing. Both halves of that argument are viewport-dependent
(`AUTO_HIDE_MIN_FIELDS` 4/3, `AUTO_HIDE_RATIO` 0.25/0.2, chosen by
`useIsMobile`), and the file pinned neither value — it was green because
happy-dom reports `innerWidth` 1024, i.e. the desktop branch.

That became load-bearing when objectui#8376 made `DetailSection`'s emptiness
test TRIM: the ACTIVITY fixture's 3 rendered rows are now 2/3 empty, which sits
exactly at the mobile minimum. Measured on this base, pinning the viewport to
375 reddens `WHITESPACE AT THE DERIVATION RUNG — HALF 2` with a bare "Unable to
find ... contract_no" that names nothing about auto-hide.

- pin `window.innerWidth` to 1280 for the existing describe, and say in a
  comment WHY desktop is the branch these cases mean to exercise;
- correct the docblock claim that `DetailSection`'s emptiness test is raw
  (objectui#8376 changed that) and record the measured per-fixture margins;
- add a second describe covering the mobile branch — not a substitute — which
  pins the distinction the desktop choice rests on: an auto-hidden row comes
  back through the "Show 2 empty fields" toggle, a deduped row never does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3473.0 KB 3512.7 KB
Main entry chunk (gzip) 143.9 KB 350 KB
Entry file index-A8jyLzXl.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) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
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) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 498.87KB 114.10KB
core (index.js) 7.48KB 2.96KB
create-plugin (index.js) 10.12KB 3.28KB
data-objectstack (index.js) 189.15KB 52.56KB
fields (index.js) 243.15KB 61.40KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
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) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 15.16KB 3.68KB
plugin-calendar (index.js) 49.00KB 13.91KB
plugin-charts (index.js) 71.39KB 19.92KB
plugin-chatbot (index.js) 194.52KB 46.34KB
plugin-dashboard (index.js) 131.48KB 34.45KB
plugin-designer (index.js) 213.21KB 43.63KB
plugin-detail (index.js) 248.68KB 63.94KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 131.01KB 32.32KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 208.58KB 56.63KB
plugin-kanban (index.js) 55.40KB 15.71KB
plugin-list (index.js) 112.74KB 27.70KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.33KB 3.25KB
plugin-view (index.js) 84.54KB 20.84KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.55KB 2.45KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 13.64KB 4.59KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
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) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
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

@os-justin
os-justin added this pull request to the merge queue Sep 8, 2026
Merged via the queue into main with commit 1706d8b Sep 8, 2026
34 checks passed
@os-justin
os-justin deleted the claude/issue-8399-dedupe-pin-pins-viewport branch September 8, 2026 00:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants