Skip to content

test(plugin-detail): state the desktop viewport in the 15 test files that depend on it (objectui#8444) - #8781

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-8444-plugin-detail-viewport-pins
Sep 9, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
claude/issue-8444-plugin-detail-viewport-pins

Conversation

@claude

@claude claude Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Fixes #8444.

Fifteen packages/plugin-detail test files now state the viewport they assert
against instead of inheriting happy-dom's ambient 1024. No assertion was changed and
nothing outside __tests__/ was touched.

The card's numbers were re-measured, and both moved

Everything below is measured on c03d03bb2 (this branch's base), not carried over from
the card's R46 reading.

card (R46) today (c03d03bb2)
test files in the package 137 151
files that depend on the desktop branch 21 27
files that state a viewport 2 16
files that depend on the branch and do not state it (not derived) 15

The 15 in the last row are this PR. ⛔ Neither number is reconciled toward the card's:
the package grew, and twelve of the twenty-seven already pin a viewport, so the
card's "only 2 of 137" is stale by an order of magnitude. Lit control on the counting
instrument: 151 of 151 test files match describe(, so a zero from that grep would be
a reading rather than a dead query.

The card's other premise holds, re-measured today: happy-dom reports innerWidth
1024 and useIsMobile() returns false — every unpinned file here runs the
desktop branch.

Arm 1 — the card's own ablation, before and after

MOBILE_BREAKPOINT 768 -> 999999 in packages/components/src/hooks/use-mobile.tsx,
then pnpm exec vitest run packages/plugin-detail/. On-disk mutation proven by
git hash-object against git rev-parse HEAD:PATH
(97358b5e… -> 7769615c…); restored by state — git diff HEAD empty for that path
and the hash back to the HEAD blob — never by an exit code; trap … EXIT INT TERM
with absolute paths.

unmutated (negative control)   Test Files  151 passed (151)          Tests  1372 passed (1372)
mutated, BEFORE this PR        Test Files   27 failed | 124 passed   Tests   112 failed | 1260 passed
mutated, AFTER this PR         Test Files   27 failed | 124 passed   Tests   112 failed | 1260 passed

The after-run is byte-identical, and the failing set is the same 27 files (diff
empty). ⚠️ That is the correct result for this change, not a failure of it: the
mutation moves the breakpoint above every finite pin, so it defeats a pinned 1280
exactly as it defeats an unpinned 1024 — the card says so itself about
DetailSection.emptinessAuthority-8376.test.tsx, which pins 1280 and still fails 2
under it. This mutation measures "depends on the desktop branch"; it structurally
cannot measure "states a viewport", which is what this PR changes.

Arm 2 — the ablation that does answer the question

So the acceptance evidence is a second ablation that moves the ambient default and
leaves MOBILE_BREAKPOINT alone: append a mobile innerWidth of 375 to
vitest.setup.dom-light.tsx, which every one of these files runs under. A file that
states its viewport overrides it; a file that inherits one does not.

Positive control first, because a green from a mutation that never arrived is the
failure mode here: a throwaway probe under the mutated setup reads
{ innerWidth: 375, isMobile: true }, so the ambient move does reach the tests.

leg A — ambient 375, this branch          Test Files  151 passed (151)        Tests  1372 passed (1372)
leg B — ambient 375, these 15 reverted    Test Files   15 failed | 136 passed  Tests   53 failed | 1319 passed

Leg B reverts exactly this PR's fifteen files to c03d03bb2 (each revert verified by
hash against the $BASE blob and asserted different from the HEAD blob), and the
fifteen files that redden are exactly the fifteen this PR pins — the two lists diff
empty. Both legs restored by state: git diff HEAD empty across the whole tree and the
setup file's hash back to its HEAD blob.

That is the reverse-verification: the pins, and only the pins, are what hold leg A green.

Each file was judged, not stamped

No file here means the mobile branch, and none is branch-independent as a whole.

  • Fourteen RelatedList.* files. RelatedList reads useIsMobile and renders a
    type="table" list two different ways: a real data-table above the breakpoint, an
    object-gallery card layout below it — no headers, no cells, no sort. Every one of
    these files reads the table: rendered headers, rendered cells, the sort the captured
    table schema carries, row actions, the pager, the derived column set.
  • declaredActionIds.twoRenderers-7182.test.tsx. Both surfaces draw through
    action-bar, which below the breakpoint collapses all but mobileMaxVisible ?? 1
    buttons into a More actions overflow. Measured on the mobile branch, its header
    control reads ['Convert Lead', 'More actions'] and the cross-surface equality
    precondition in its first case fails — the comparison the file is built on only holds
    while both toolbars are flat.
  • Two files carry a second describe that the branch does not reach —
    RelatedList.sortabilitySignal (surface 2) and RelatedList.unmaterializedSort
    (the data-list describe). Both exercise type='list', and the mobile carve-out
    applies to grid/table only. The file-level pin states their branch without
    changing it, and both docblocks say exactly that rather than implying the whole file
    depends on it.

No shared helper — deliberately

The card floated "a single shared pinViewport helper" as a shape for a triager. The
ruling's binding constraint is the other one: follow objectui#8399's spelling, ⛔ do not
invent a second way of declaring a viewport. All sixteen files in this package that
already pin one use an in-file beforeAll (or beforeEach) doing
Object.defineProperty(window, 'innerWidth', { configurable: true, value: 1280 }), so a
shared module would be the second way, with sixteen files left on the first. The
suggestion is resolved against the constraint; ⛔ this is not an open question.

Gates

scripts/pm/dispatch-gates.mjs does not exist in this repo, so the family is derived by
hand from the changed-file surface — fifteen *.test.tsx under
packages/plugin-detail/src/__tests__/ plus one .changeset/*.md.

gate exit note
pnpm exec vitest run packages/plugin-detail/ 0 151 passed (151) / 1372 passed (1372)
pnpm --filter @object-ui/plugin-detail run type-check 0 tsc --noEmit && tsc -p tsconfig.test.json; --listFiles shows 105 files under src/__tests__/, the edited ones among them, so the tests are covered rather than assumed
eslint . in packages/plugin-detail 0 205 files, 0 errors, 963 warnings — all pre-existing no-explicit-any; 0 messages land on a line this PR adds
node scripts/check-changeset-presence.mjs 0 red before the changeset, green after
node scripts/check-changeset-fixed.mjs 0
node scripts/check-changeset-no-major.mjs 0
node scripts/check-control-bytes.mjs 0 6972 tracked text files
node scripts/check-vi-mock-specifiers.mjs 0 these files carry vi.mock call sites
node scripts/check-vi-mock-inherit.mjs 0 same reason
node scripts/check-comment-mask-corpus.mjs 0 this PR adds block comments; the 1-file/1517-byte residue is the ceiling objectui#7882 holds open, unchanged

pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-detail^...' build was run
first (exit 0) — before it, type-check failed only on unbuilt dist/*.d.ts.

Narrowing declared. pnpm lint is turbo run lint across every package; this run is
one package. Universe read from eslint's own resolution, not guessed — eslint . is
verbatim the package's own lint script, and the 205-file count is read from
--format json. Invariance: eslint.config.js enables no type-aware linting (no
project, projectService or parserOptions project wiring), so verdicts are per-file
and a diff confined to fifteen files plus a new changeset cannot move any untouched
file's result. The remaining check:* farm belongs to CI.

node scripts/check-unreferenced-sources.mjs is not in the family: it guards every
non-test source file, and this diff is entirely tests.

Not a governed surface — docs/adr/**, .claude/**, skills/**, AGENTS.md,
CLAUDE.md are untouched.

Changeset

Empty frontmatter, the explicit "this releases nothing" declaration. These files live
under packages/plugin-detail/src/, which the gate guards for every package in the
fixed group, so the declaration is owed even though it is test-only.

Scope

⛔ No detector was built. The triage ruling put that in a separate domain:devx card
starting from this card's ablation procedure, and explicitly not with the taker of this
one. One PR, not fifteen. Nothing else in these tests was touched — every hunk is an
addition of a docblock, a beforeAll, and beforeAll on the existing vitest import.


Generated by Claude Code

…depend on it (objectui#8444)

Re-measured on `c03d03bb2` with the card's own ablation — `MOBILE_BREAKPOINT`
768 -> 999999 in `packages/components/src/hooks/use-mobile.tsx`, then
`pnpm exec vitest run packages/plugin-detail/`:

  unmutated  151 passed (151)            1372 passed (1372)
  mutated     27 failed | 124 passed     112 failed | 1260 passed

The card's 21-of-137 was measured at R46; the package is 151 files today and
27 of them depend on the desktop branch. 12 of those 27 already state a
viewport (the mutation is stronger than a pin — it defeats a pinned 1280 too),
so the files that inherit the branch rather than choosing it are the other 15.

Each is judged, not stamped. All 14 `RelatedList.*` files read the embedded
`data-table` — headers, cells, sort, row actions, pagination — and below the
breakpoint `RelatedList` renders an `object-gallery` card layout instead, with
no table at all. `declaredActionIds.twoRenderers-7182` compares two toolbars
that `action-bar` only draws flat above the breakpoint; on the mobile branch
its header control reads ['Convert Lead', 'More actions'] and the file's
equality precondition fails. Two files (`sortabilitySignal`,
`unmaterializedSort`) carry a second describe over `type='list'`, which the
mobile carve-out never touches — the pin states their branch without changing
it, and the docblocks say so.

Spelling is objectui#8399's, unchanged: a file-level `beforeAll` doing
`Object.defineProperty(window, 'innerWidth', { configurable: true, value: 1280 })`,
which is what the 16 files in this package that already pin one do. No second
way of declaring a viewport was introduced, and nothing else in these tests
was touched.

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

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3484.2 KB 3512.7 KB
Main entry chunk (gzip) 144.0 KB 350 KB
Entry file index-BDdX9raw.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) 499.42KB 114.32KB
core (index.js) 7.48KB 2.96KB
create-plugin (index.js) 10.12KB 3.28KB
data-objectstack (index.js) 198.39KB 55.29KB
fields (index.js) 245.88KB 62.05KB
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) 13.52KB 4.88KB
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) 8.39KB 3.10KB
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.03KB 13.93KB
plugin-charts (index.js) 71.39KB 19.92KB
plugin-chatbot (index.js) 194.53KB 46.34KB
plugin-dashboard (index.js) 131.43KB 34.44KB
plugin-designer (index.js) 215.51KB 44.29KB
plugin-detail (index.js) 252.39KB 65.32KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 134.25KB 33.52KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 210.67KB 57.19KB
plugin-kanban (index.js) 55.50KB 15.75KB
plugin-list (index.js) 112.73KB 27.69KB
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.55KB 3.32KB
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 (strict-authoring-face.js) 14.27KB 5.47KB
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

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

Projects

None yet

2 participants