Skip to content

fix(plugin-dashboard,types): object-metric refuses drillDown.filter and drillDown.mode on its prop type - #10681

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-9002-metric-drilldown-refusal
Sep 25, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-9002-metric-drilldown-refusal

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #9002
Clause-②: no

What this does

This implements ruling B on objectui#9002 (comment 5643445104), a per-block refusal. drillDown.filter and drillDown.mode are refused on the object-metric block's prop type. The shared DrillDownConfig keeps both keys for the blocks that read them.

  • @object-ui/types: adds ObjectMetricDrillDownConfig beside DrillDownConfig in data-display.ts. It is DrillDownConfig plus filter?: never and mode?: never tombstones. They use the repo's existing idiom (?: never, a REFUSED BY NAME docblock, @deprecated, as on PivotTableSchema.body), and each docblock names the blocks that do read the key:
    • filter: object-chart and object-pivot, through computeDrillFilter;
    • mode: object-data-table, on its row click.
  • @object-ui/plugin-dashboard: ObjectMetricWidgetProps.drillDown now uses the new type. The prop docblock and the drawer-section comment ("deliberately NOT forwarded") now cite the ruling. They no longer defer to "the judgement objectui#8970 asks for".
  • Registration: the object-metric registration's drillDown input description gains one sentence. It says the two keys do not apply to a metric, and names where they do apply.
  • Changeset: one changeset, both packages at patch.
  • Existing test header: objectMetricDrillDownMembers-8071.test.tsx gets an amendment note. Its advice against a narrower metric drill schema is now ruled for these two members. Its recorded measurements are unchanged.

Premise re-measured on origin/main d08ab2fc8

object-metric still reads neither key. These are all the reads of the metric's drillDown:

  • isDrillEnabled reads enabled;
  • resolveDrillTitle reads title;
  • DrillDownDrawer receives target, columns, maxRows and report.

DrillDownDrawer takes a filter prop that is already computed (the metric's own resolved filter). Its inner table gets a hard-coded { enabled: true, mode: 'record', target: 'dialog' } and never the authored config.

Across the repo, drillDown.filter is read only through computeDrillFilter (called by ObjectChart and ObjectPivotTable). drillDown.mode is read only by ObjectDataTable.

Which door sees an authored object-metric drillDown: measured, in order

door reading sees the two members?
Published TS type ObjectMetricWidget is exported from the package entry, typed as React.FC of ObjectMetricWidgetProps. The props interface is not exported by name, but React.ComponentProps and JSX reach it. yes, narrowed here
Registry inputs and manifest drillDown is type: 'object' with no of. Codegen emits a Record of string to unknown, and validateTree checks only the coarse kind. no
objectui validate / objectui check safeValidateSchema (AnyComponentSchema) has no object-metric arm, so every object-metric node is refused at the type discriminator, with or without drillDown (probe below). check uses it only as a recogniser, alongside the known-type list. no
Spec In @objectstack/spec 17.4.0, ObjectMetricPropsSchema.drillDown is z.unknown(). ChartDrillDownSchema is the chart-only REACT-TIER record and does not govern this block. key only, not its members

The probe runs tsx over packages/types/src/zod/index.zod.ts, calling safeValidateSchema:

no drillDown (control)          => success=false invalid_union@type: Invalid input
drillDown target only           => success=false invalid_union@type: Invalid input
drillDown.filter                => success=false invalid_union@type: Invalid input
drillDown.mode                  => success=false invalid_union@type: Invalid input
object-data-table control       => success=true
object-data-table bad drillDown => success=false invalid_type@drillDown.enabled: expected boolean, received string

The last two rows are the positive control: on a block it has an arm for, the same door does reach drillDown.

⇒ No existing runtime door parses the members of an object-metric drillDown, so, as the dispatch instructed, no validator and no runtime channel was added. ⚠️ A stored JSON metric config carrying either key is still accepted and ignored at render. The refusal is at the TypeScript door only. The changeset banner says so instead of using the ruling's 「refused at the door」 wording. The PM decides whether that wording holds.

Verification (HEAD fdaeab1b1; the patch round's re-runs at ecff10661 are under Acceptance notes)

  • Build of the dependency closure, pnpm --filter "@object-ui/plugin-dashboard^..." run build: lock VERDICT command-exit 0. The new interface is present in packages/types/dist/data-display.d.ts.

  • pnpm --filter @object-ui/types run type-check and pnpm --filter @object-ui/plugin-dashboard run type-check: VERDICT command-exit 0. --listFilesOnly confirms each program compiles its pin. It also shows that plugin-dashboard's test program reads @object-ui/types through dist/data-display.d.ts, not src.

  • pnpm exec vitest run packages/plugin-dashboard/src/ packages/types/src/__tests__/, from the repo root: Test Files 371 passed (371), Tests 6526 passed (6526). A verbose rerun of the named suites (both new pins, drill-down declared-keys and mirror, zod-mirror-parity, both widget-schema-anchor pins, and the metric drill suites) gave 10 passed, 160 passed.

  • Cross-package suites that load the registration (registry-inputs-spec-parity, public-contract, ga-honoured-inputs-author-reach, component-input-union-specimens, packages/sdui-parser): 21 passed, 459 passed.

  • Gates, each with exit 0:

    • check:control-bytes: OK.
    • check:new-line-citations: 0 new citation(s).
    • check-changeset-presence: 6 source files of 2 released packages, 1 changeset.
    • changeset:check: no major.
    • check:changeset-claims: 8 pending changesets name data-display.ts. All 8 were read, and all describe other regions and stay true.
    • check:spec-symbols, check:doc-types, check:pending-changeset-literals, check:component-surface-parity, check:element-data-source-declaration, check:registry-bare-names, check:phantom-deps, check:unused-deps, check:self-import, check:esm-specifiers, check:test-path-roots, check:unreferenced-sources, check:lint-rule-coverage, check:vi-mock-specifiers, type-check:coverage.
    • check-governed-queue-guard --test over the 7 paths: NOT GOVERNED.
  • NOT MEASURED: check:sdui-registration-pins exited 2 (No console build to weigh at apps/console/dist/assets), a prerequisite that was not met. The gate judges registrations dropped at bundle time. This diff edits only the description text of an existing input and touches no sideEffects array. Left to CI.

  • ESLint was run on the 6 touched source and test files only, not the whole repo. The narrowing is safe for three reasons:

    • the config is the root eslint.config.js, and neither package has its own;
    • --format json reports 6 files, 0 errors and 52 warnings, all pre-existing (no-explicit-any, react-refresh, react-hooks), none on an added line;
    • the resolved config has parserOptions {}, so there is no type-aware linting, and no rule in eslint-rules/ reads the disk. The diff cannot change the verdict on any untouched file.

    The full pnpm lint is left to CI.

Ablation: the pins fail without the narrowing (both mutations committed first, applied through ablation-replace.mjs, restored by blob)

A. The narrowing removed. The anchor export interface ObjectMetricDrillDownConfig extends DrillDownConfig { became export type ObjectMetricDrillDownConfig = DrillDownConfig; export interface AblatedMetricTombstones9002 {, which leaves the tombstones on an unrelated interface. The anchor count went 1 to 0, and the blob went a8934d1c74ba to 4790116b9ce8.

  • @object-ui/types test program: exit 2, three TS2578 Unused '@ts-expect-error' errors, on the fresh filter literal, the fresh mode literal, and the widened DrillDownConfig handed across.
  • The types package was rebuilt (exit 0), and the marker count in dist/data-display.d.ts was 1. The plugin-dashboard test program then exited 2 with two TS2578 errors, on the JSX filter and mode lines.
  • Restore: the blob equals HEAD a8934d1c74ba and git diff HEAD is empty. The dist restore leg was a rebuild (exit 0) that left the marker count at 0 and the tombstone at 1. Both test programs then exited 0, and git status --porcelain was empty.
  • The first attempt was a no-op. The tool refused it because the replacement contained the anchor, so the anchor count could not drop, and it proved the restore. Nothing was measured on that attempt.

B. The widget wiring removed. The anchor drillDown?: ObjectMetricDrillDownConfig; became drillDown?: import('@object-ui/types').DrillDownConfig;, and the blob went 3b41b51f0045 to 835d48b78e36.

  • The plugin-dashboard test program exited 2: TS2344 on _PropIsTheMetricShape, plus two TS2578 errors.
  • ⚠️ The plugin-dashboard source program (tsc --noEmit) stayed at exit 0 under this mutation. The pin is the only thing guarding the wiring.
  • Restored: the blob equals HEAD 3b41b51f0045 and the diff is empty.

Direction: red as predicted in both cases. ablation-dist-preflight.mjs could not be used here because it resolves its repo root from its own location, which is the sister checkout. The dist proof is the marker count above: 1 under the mutation, 0 after the restore rebuild.

Acceptance notes

  • Root export: closed in the PM patch round (head ecff10661). The seat answered open question 2 with A and extended the claim's surface to packages/types/src/index.ts. ObjectMetricDrillDownConfig is now on the root export list beside DrillDownConfig, and the widget imports it from @object-ui/types like every other type in that file, so the emitted ObjectMetricWidget.d.ts also resolves under node10. The changeset names both the root entry and the @object-ui/types/data-display subpath. The patch round re-ran: the closure build, both type-checks, plugin-dashboard build, 15 suites / 169 tests (the 9002 pins, 8071, 8970, the widget-schema anchors, zod-mirror parity and the root-barrel / export census suites), check:spec-symbols, check:changeset-claims, check:control-bytes, check:new-line-citations and check:self-import, all exit 0.
  • Adjacent ledger text, still true. The object-metric.drillDown row in registry-inputs-spec-parity.test.ts (in apps/console, outside this surface) records filter and mode as having no read site on this block and as deliberately not asserted. Both statements remain true: nothing at runtime changed.
  • Shared docblock not touched (outside the surface). The shared DrillDownConfig.mode docblock says the 'filter' drill-through mode is "Used by charts, pivot tables and metric cards". None of the three reads mode. This is raised in the report together with the sibling gap: chart and pivot never read mode, and the data table never reads the drill filter. The seat filed this with the sibling gap as objectui#10685.
  • Collision guard. The data-display.ts hunk sits between DrillDownConfig and PivotTableSchema, and does not overlap the TableColumn region that objectui#10643 edits. data-display.zod.ts, DataTableSchema and TableColumnSchema are untouched.

Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC


Generated by Claude Code

…nd drillDown.mode on its prop type

Ruling B on objectui#9002, a per-block refusal: `ObjectMetricDrillDownConfig`
in `@object-ui/types` is `DrillDownConfig` with `filter?: never` and
`mode?: never` tombstones naming the blocks that do read each key, and
`ObjectMetricWidget`'s `drillDown` prop takes it. The shared type is
unchanged. The `object-metric` registration's `drillDown` description says
the same in one sentence.

Refused at the TypeScript door only: no runtime validator here reads the
members of an `object-metric` `drillDown`.

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

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 9 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/6051-gantt-flat-config-declared-keys.md

  • names packages/types/src/index.ts → packages/types/src/index.ts — edited by this change

    GanttConfig itself gains nine members and is a published type, exported by name from packages/types/src/index.ts: lockField, objectField, summaryExtent, defaultCollapsedDepth, borderColorField, dependencyTypes, timeZone, exportFileName, interactions. The entry file's diff is empty only because the export list already named the type — the widening happened at the declaration.

.changeset/6349-types-internal-name-collisions-batch-1.md

  • names data-display.ts → packages/types/src/data-display.ts — edited by this change

    BreadcrumbItem / BreadcrumbSchema — re-pointed, because one copy was stale. Both were declared in data-display.ts and in navigation.ts. The data-display pair was not a second dialect but a strict SUBSET: no key declared differently on either side, and missing BreadcrumbItem.icon / onClick / siblings and BreadcrumbSchema.maxItems. Everything that reads a breadcrumb was already on the navigation declaration — registry.ts maps the 'breadcrumb' component type to it, src/index.ts re-exports it under the bare names, zod/navigation.zod.ts mirrors it (icon, onClick, siblings, maxItems included), the ui:breadcrumb renderer consumes it, and the component's own documentation page documents icon and maxItems. data-display.ts now re-exports the one authority.

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

  • names data-display.ts → packages/types/src/data-display.ts — 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/6951-tree-view-data-retired.md

  • names data-display.ts → packages/types/src/data-display.ts — edited by this change

    Two published faces, one retirement — and why a tombstone, not a deletion. The TypeScript interface TreeViewSchema (@object-ui/types, data-display.ts) declares data?: never; the Zod mirror TreeViewSchema (@object-ui/types/zod, data-display.zod.ts) declares data as a retirementTombstone(). BaseSchema already declares data?: any (z.any().optional() on the mirror), so DELETING the member would not have refused the key — it would have ADMITTED it, unvalidated, through the base member, and the renderer would have drawn an empty tree. The tombstone on the extended schema shadows the base member on both faces; the pin measures the base accepting the very document the extended schema refuses.

.changeset/6972-markdown-inert-keys-retired.md

  • names data-display.ts → packages/types/src/data-display.ts — edited by this change

    What was measured, on this branch's base. sanitize was declared ?: boolean with @default true on both published faces — data-display.ts and the Zod mirror — documented, and read by NOTHING. Worse than an ordinary inert key, it implied a switch that does not exist: sanitization is unconditional. rehypePlugins in plugin-markdown/src/MarkdownImpl.tsx is a module-level const array whose last link is [rehypeSanitize, sanitizeSchema], handed to ReactMarkdown as-is — no ternary, no if, no runtime assembly. MarkdownRenderer forwards exactly content and className, and MarkdownImplProps accepts only those two. A repo-wide grep for schema.sanitize over packages/ and apps/ returns nothing, against a control of 20 .tsx files reading schema.content in the same query shape, so the zero is a reading, not a blind query. An author writing sanitize: false believed they turned XSS filtering off; one writing sanitize: true believed they turned it on. Neither was true.

.changeset/7722-wrapper-class-five-more.md

  • names data-display.ts → packages/types/src/data-display.ts — edited by this change

    Each of renderers/form/switch.tsx, textarea.tsx, date-picker.tsx, select.tsx and renderers/data-display/list.tsx reads schema.wrapperClass onto its wrapper element, and neither the TypeScript interface (form.ts, data-display.ts) nor the zod mirror (zod/form.zod.ts, zod/data-display.zod.ts) declared the key. The reads compiled through BaseSchema's index signature (objectui#5155) and the values parsed through .passthrough(), admitted unexamined. The same key, on the same class of read, is declared on CheckboxSchema (objectui#6938), FileUploadSchema and FilterBuilderSchema (objectui#6150); these five were left out only because their doc pages never listed it.

.changeset/8331-data-table-empty-action-primitive-node.md

  • names packages/types/src/data-display.ts → packages/types/src/data-display.ts — edited by this change

    Behaviour change on a published surface, deliberately. DataTableSchema.emptyAction is declared SchemaNode on both published faces — packages/types/src/data-display.ts and its Zod twin in packages/types/src/zod/data-display.zod.ts — and SchemaNode is BaseSchema | string | number | boolean | null | undefined. The empty-state render path additionally required typeof … === 'object', so an authored emptyAction: 'Create the first record' rendered nothing and reported nothing: declared wider than enforced, failing in the direction that loses the author's content without a diagnostic. A node that renders nothing today therefore starts rendering.

.changeset/9256-list-timeline-content-channels.md

  • names packages/types/src/data-display.ts → packages/types/src/data-display.ts — edited by this change

    These two were held out of the previous family-D slice for a SERIAL constraint on packages/types/src/data-display.ts and never for a verdict. Readership was re-derived for both rather than inherited: a TypeScript compiler-API sweep files every .body / .children read under the declared type of its receiver and answers zero for ListSchema and TimelineSchema while its live controls fire. timeline's bare-key owner is any-typed, so it was attributed directly as well — packages/plugin-timeline contains no channel read of any kind.

.changeset/table-renderer-declared-column-contract-5350.md

  • names packages/types/src/data-display.ts → packages/types/src/data-display.ts — edited by this change

    renderers/complex/table.tsx resolved a heading as col.header || col.label and a cell as row[col.accessorKey || col.name]. Neither label nor name is declared on TableColumn, which declares header and accessorKey — both required (packages/types/src/data-display.ts). This was the fourth site of the column-alias family, after data-table, ObjectDataTable and ObjectGrid (objectui#5350).

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.

Compared the checked-out tree with 7baede374 (merge-base with origin/main): 7 file(s) changed outside .changeset/, read against 1516 pending declaration(s) that publish a body (2102 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3055.8 KB 3104.5 KB
Main entry chunk (gzip) 147.9 KB 350 KB
Entry file index-nrtvwJI-.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.57KB 6.15KB
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) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.17KB 10.58KB
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.39KB 1.21KB
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.52KB 3.45KB
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.13KB 7.95KB
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) 547.22KB 130.96KB
core (index.js) 9.52KB 3.79KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 223.91KB 62.28KB
fields (index.js) 259.19KB 65.61KB
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.40KB 12.91KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
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.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.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.01KB 3.93KB
plugin-calendar (index.js) 51.49KB 14.64KB
plugin-charts (index.js) 76.80KB 21.35KB
plugin-chatbot (index.js) 198.36KB 47.20KB
plugin-dashboard (index.js) 133.88KB 35.53KB
plugin-designer (index.js) 216.25KB 44.39KB
plugin-detail (index.js) 232.85KB 61.58KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 152.55KB 39.16KB
plugin-gantt (index.js) 169.71KB 41.93KB
plugin-grid (index.js) 216.65KB 59.28KB
plugin-kanban (index.js) 48.35KB 15.08KB
plugin-list (index.js) 115.77KB 28.75KB
plugin-map (index.js) 23.01KB 7.60KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.75KB 8.98KB
plugin-tree (index.js) 10.52KB 3.69KB
plugin-view (index.js) 87.77KB 21.99KB
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) 116.21KB 38.14KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
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) 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) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
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

The widget now imports it from `@object-ui/types` like every other type in
the file, so the emitted `ObjectMetricWidget.d.ts` names the root entry
rather than the `./data-display` subpath (which a node10-resolution
consumer cannot resolve: the package has no `typesVersions`). The changeset
says the type is published on both entries. PM patch round on objectui#9002.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3055.8 KB 3104.5 KB
Main entry chunk (gzip) 147.9 KB 350 KB
Entry file index-nrtvwJI-.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.57KB 6.15KB
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) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.17KB 10.58KB
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.39KB 1.21KB
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.52KB 3.45KB
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.13KB 7.95KB
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) 547.22KB 130.96KB
core (index.js) 9.52KB 3.79KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 223.91KB 62.28KB
fields (index.js) 259.19KB 65.61KB
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.40KB 12.91KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
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.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.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.01KB 3.93KB
plugin-calendar (index.js) 51.49KB 14.64KB
plugin-charts (index.js) 76.80KB 21.35KB
plugin-chatbot (index.js) 198.36KB 47.20KB
plugin-dashboard (index.js) 133.88KB 35.53KB
plugin-designer (index.js) 216.25KB 44.39KB
plugin-detail (index.js) 232.85KB 61.58KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 152.55KB 39.16KB
plugin-gantt (index.js) 169.71KB 41.93KB
plugin-grid (index.js) 216.65KB 59.28KB
plugin-kanban (index.js) 48.35KB 15.08KB
plugin-list (index.js) 115.77KB 28.75KB
plugin-map (index.js) 23.01KB 7.60KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.75KB 8.98KB
plugin-tree (index.js) 10.52KB 3.69KB
plugin-view (index.js) 87.77KB 21.99KB
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) 116.21KB 38.14KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
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) 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) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
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 September 25, 2026 17:33
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 25, 2026
Merged via the queue into main with commit 526fc11 Sep 25, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-9002-metric-drilldown-refusal branch September 25, 2026 17:45
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Sep 28, 2026
…se the DrillDownConfig members they never read (objectui#10685) (objectstack-ai#10710)

Fixes objectstack-ai#10685
Clause-②: no — a per-block narrowing, as the ruling recorded for
objectui#9002

## What this does

This applies objectui#9002's ruling B (comment `5643445104`, a per-block
refusal) to the sibling blocks of `object-metric`, as triage
`5837818071` directed. Each block refuses, by name, the
`DrillDownConfig` members it can never honour; the shared
`DrillDownConfig` keeps every member for the blocks that read them. The
seat's round-2 ruling (option A) widened the claim's file surface by
`packages/types/src/objectql.ts` and
`packages/types/src/zod/objectql.zod.ts`, so the `object-data-table`
half shuts BOTH doors here, and merging this PR completes the card.

- **`@object-ui/types`** adds two per-block shapes beside
`ObjectMetricDrillDownConfig` in `data-display.ts`, exported on the root
entry and on the `@object-ui/types/data-display` subpath:
- `ObjectPivotDrillDownConfig`: `DrillDownConfig` plus a `mode?: never`
tombstone (the objectstack-ai#10681 idiom: a `REFUSED BY NAME` docblock and
`@deprecated`, naming `object-data-table` as the block that reads
`mode`).
- `ObjectDataTableDrillDownConfig`: `DrillDownConfig` plus `filter?:
never`, `maxRows?: never` and `report?: never` tombstones, and `target?:
'drawer' | 'dialog'`. Each tombstone names the blocks that do read the
key.
- **TypeScript door.** `ObjectDataTableSchema.drillDown` in
`objectql.ts` is typed with `ObjectDataTableDrillDownConfig`. The data
table's component prop is anchored `Equal` to that schema by the two
objectui#6576 pins, so the widget's prop refuses exactly what the schema
refuses.
- **Zod door.** The `object-data-table` mirror's `drillDown` in
`objectql.zod.ts` is the shared `DrillDownConfigSchema` extended so that
`filter`, `maxRows` and `report` are `retirementTombstone` arms whose
messages name the key and the blocks that read it, and `target` is
`z.enum(['drawer', 'dialog'])` with an error naming the refused
`'navigate'`. `enabled`, `mode`, `title` and `columns` parse exactly as
before. Nothing else in `objectql.zod.ts` changes. This is the door
`objectui validate` and `objectui check` run (`safeValidateSchema`), so
a stored JSON table config carrying one of the four is now refused
there.
- **`@object-ui/plugin-dashboard`**: `ObjectPivotTableProps.schema`
gains `drillDown?: ObjectPivotDrillDownConfig`, and the component reads
its drill config through that type instead of through `any`.
- **Docblocks**: the shared `DrillDownConfig.mode` docblock names only
`object-data-table` (none of charts, pivot tables or metric cards reads
it) and says what each value does there; the `DrillDownConfig` class
docblock no longer says "`target` is honoured by all of them", and the
declared-keys pin's comment no longer names the table among the
`navigateOnly` deliverers.
- **`object-chart`: no type change** (see H4). Its refused set is
pinned.
- **Pins**: `drill-down-per-block-10685.test.ts` in `@object-ui/types`
(one `describe` per block, `@ts-expect-error` per refused member with
the read members as controls, plus a RUNTIME `describe` over the zod
door), and `ObjectPivotTable.drillDownRefusal-10685.test.tsx` in
`@object-ui/plugin-dashboard` (the published component's prop). The two
6576 anchor pins are re-pointed at the table shape, and the 7352 mirror
pin's table leg runs over the values the table now accepts, with a
non-vacuity check that the host-synthesised table configs stay in it.
- **Changesets**: `10685-drilldown-per-block.md` (`@object-ui/types`
`minor` with a breaking banner, `@object-ui/plugin-dashboard` `patch`)
opens with the banner "Breaking for authored metadata, graded `minor` by
this repo's convention": `objectui validate` / `safeValidateSchema` now
refuse a stored `object-data-table` config carrying `drillDown.filter`,
`.maxRows`, `.report` or `target: 'navigate'` that parsed green before,
none was ever read, and the migration is to delete the key or write
`'drawer'` / `'dialog'`. `minor` because the change narrows the
published validator's accept set and the published
`ObjectDataTableSchema` type, the grade every pending zod-door refusal
in this repo carries (6881, 6951, 6972, 7322, 7963, 8801, 7352);
objectui#9002's `patch` was graded on a TypeScript-door-only refusal,
which does not describe this change. `@object-ui/plugin-dashboard` stays
`patch`: a React-prop narrowing with no runtime door, the 9002 shape.
The changeset also says `object-pivot`'s refusal is at the TypeScript
door only (no zod mirror exists for it). H6:
`.changeset/7352-drill-down-config-mirror.md` and
`.changeset/6576-widget-schema-anchors.md` each have the falsified
sentence scoped "at this change" plus a `⚠️ Dated note, 2026-09-25 …
objectui#10685`; frontmatter md5 before and after: 7352
`ecedb10c5189b2c8841ea736b67c3cc6` both, 6576
`b25dbc26fe50c14d0b6f0cc8fe6cb9e1` both.

## H1: the table, re-measured per block

Read sites were re-read on `origin/main` `5c61e524` this round. The
one-member-at-a-time runtime probe (rendered DOM, data source calls and
`openRecordList` calls compared against a control, with and without a
`DrillNavigationProvider`) was run in round 1 at `bc97f9247` and is
recorded in report comment `5839013332`; it was not re-run, and no read
site moved since.

| block | reads | never reads |
|:--|:--|:--|
| `object-chart` | `enabled` (`isDrillEnabled`), `filter`
(`computeDrillFilter`), `title` (`resolveDrillTitle`), `target`
including `'navigate'` (`openRecordList`), `maxRows` (drawer
`pageSize`), `columns` | `mode`, `report`: already refused by the spec's
`ChartDrillDown` type and by the zod mirror (`unrecognized_keys`) |
| `object-pivot` | `enabled`, `filter`, `title`, and `target`,
`columns`, `maxRows`, `report` through `DrillDownDrawer` | `mode` |
| `object-data-table` | `enabled` (`isDrillEnabled`), `mode` (`'record'`
opens the row; `'filter'` turns the drill off), a non-template `title`,
`columns` (the record drawer's field list), `target` in two arms
(`'dialog'`, anything else drawn as a drawer) | `filter`, `maxRows`,
`report`; `target: 'navigate'` was drawn as a drawer |

`@object-ui/core`'s `drill-down.ts` helpers read `enabled`, `filter` and
`title` only; `DrillNavigationContext` carries one handler,
`openRecordList(objectName, filter)`; the `data-table` renderer the
table spreads its node into never names `drillDown`;
`RecordDetailDrawer` takes `fields`, `title` and a two-armed `target`.

The card's runtime-door bullet also names `ChartSchema.drillDown`. That
is the plain `chart` node, whose renderer `ChartRenderer.tsx` reads no
`drillDown`; it is outside this card's three blocks and deliberately
untouched, the plain-`chart` remainder.

## H2: can never honour, or not yet honoured (one line each)

- `object-pivot` · `mode`: never. Every pivot click point is an
aggregated bucket (a cell, a row or column header, a total), so the
pivot always drills through; `mode` chooses drill-to-record for a
clicked ROW, and a pivot has none. This is the metric's reasoning in
`5643445104`.
- `object-chart` · `mode`, `report`: never, by the contract. Its drill
type is `@objectstack/spec`'s `ChartDrillDown`, which declares neither;
the spec's schema refuses both by name ("A chart segment is always an
aggregate"). Already refused before this card; only pinned here.
- `object-data-table` · `filter`, `maxRows`, `report`: never. All three
configure a drilled LIST (its scope, its row cap, the report that
replaces it). The table drills to the RECORD its row already is,
`RecordDetailDrawer` lists nothing, and the block's `mode: 'filter'` arm
is pinned as "ignored", so there is no list drill for them to act on. A
row that drills through to a list of another object would need a
drill-target member `DrillDownConfig` does not have: a new capability,
not an unread member.
- `object-data-table` · `target: 'navigate'`: refused, not honoured
(H3).

## H3: `navigate` on the data table

`object-chart` honours `navigate` by calling `openRecordList(objectName,
merged filter)`, the object's LIST page scoped by the drill filter.
`DrillNavigationContext` has no record-level handler and
`@object-ui/app-shell`'s `useOpenRecordList` builds only the list route.
The table's click point is one record, so the list page is the wrong
destination, and a "navigate to this record" arm needs a new host seam,
not a small hunk. So the table's shape refuses `navigate` on both doors
and an author gets a type error, or a `validate` refusal, instead of a
drawer.

## H4: where the shape differs from PR objectui#10681

- **`object-chart` keeps its spec-bound type.**
`ObjectChartSchema.drillDown` is already per-block (the spec's
`ChartDrillDown`), pinned `Equal` to that symbol by
`object-chart-undeclared-keys-8885.test.ts`. A tombstoned local type
would unbind it from the spec. A fresh `mode` or `report` literal is
already a compile error and the zod mirror already refuses both by name.
- **Registration sentences: none added.** The `object-pivot` and
`object-data-table` registrations advertise no `drillDown` input (adding
one widens the designer palette, a shape decision). `object-chart`'s
description is pinned by `packages/plugin-charts/src/index.test.ts` to
list exactly the six spec keys and NOT `mode` or `report`.
- **Zod door narrowed for the table only**, because a runtime validator
(`safeValidateSchema`, run by `objectui validate`) reads that block's
drill members and accepted all four; the round-1 probe showed the same
door refuses a bad value on `enabled`, so it does reach `drillDown`.
`object-pivot` has no zod mirror, so its refusal is TypeScript-only and
the changeset says so.

## Verification (measured at `430223947f` = `a1217b515` merged with
`origin/main` `5c61e524`; head `64d43b6d67` = that head + a second merge
of `origin/main` `41ae65b26` + the changeset commit)

- **Round 3 delta and its gates (head `64d43b6d67`).** Against
`430223947f` the 14 files of this card differ in two places only: the
changeset (the `minor` grade and its banner) and one line of `index.ts`
that `main`'s own objectstack-ai#10730 deleted (a retired `ColumnWidthConfig` export);
the other 12 are byte-identical, and the branch's diff against `main` is
still exactly these 14 files. Re-run at this head, each exit 0:
`check-changeset-presence` (11 source files of 2 released packages, 1
changeset); `changeset:check`; `check-changeset-no-major` (the
`Changeset Bump Policy` step); `check:changeset-claims` (36 pending
changesets, unchanged reading); `check:pending-changeset-literals`;
`check:new-line-citations` (`0 new citation(s)`); `check:control-bytes`
OK; `check-changeset-overwrite` (report-only: it reports the 6576 and
7352 edits, the intended shape). No suite, type-check or ablation was
re-run: the delta is a changeset plus `main`'s already-green commits
merged with a clean `merge-tree`, and none of the measured sources
moved.

- Closure build `pnpm --workspace-concurrency=2 --filter
'@object-ui/plugin-dashboard^...' run build`, under the verify lock:
`VERDICT command-exit 0`. `dist/data-display.d.ts` and
`dist/objectql.d.ts` carry `ObjectDataTableDrillDownConfig` (2 hits
each), `dist/index.d.ts` carries `ObjectPivotDrillDownConfig`.
- `pnpm --filter @object-ui/types run type-check` then `pnpm --filter
@object-ui/plugin-dashboard run type-check`: `VERDICT command-exit 0`.
`tsc --listFilesOnly` on each `tsconfig.test.json` lists the four types
pins and the two plugin-dashboard pins, and plugin-dashboard's program
reads `@object-ui/types` through `dist/data-display.d.ts` and
`dist/objectql.d.ts`.
- `pnpm exec vitest run --maxWorkers=2 packages/types/
packages/plugin-dashboard/` from the repo root: `Test Files 378 passed
(378)`, `Tests 6562 passed (6562)`, `VERDICT command-exit 0`.
- Gates, each exit 0: `check-changeset-presence` (11 source files of 2
released packages, 1 changeset); `check:new-line-citations` (`0 new
citation(s)`); `check:control-bytes` OK; `changeset:check` (no `major`);
`check:pending-changeset-literals`; `check:changeset-claims` (36 pending
changesets name a touched file; the two whose prose is about drill-down,
7363 and 8885, were read and stay true, and the rest describe other
regions of these files); `check:spec-symbols`; `check:self-import`;
`check:esm-specifiers`; `check:test-path-roots`;
`check:vi-mock-specifiers`; `check:handler-key-reads`;
`check:unreferenced-sources`; `check:metadata-write-doors`;
`type-check:coverage`; `check:component-surface-parity`;
`check:doc-types`; `check-governed-queue-guard --test` over the 14
paths: NOT GOVERNED.
- NOT MEASURED: `check:readme-exports` exits 1 on a prerequisite (385
README self-imports unjudgeable because other packages'
`dist/index.d.ts` are not built); this diff edits no README. Left to CI.
- ESLint was run on the 11 touched source and test files, not the whole
repo. The narrowing is a measurement, for three reasons: the config is
the root `eslint.config.js` and neither package has its own; `--format
json` reports 11 files, 0 errors and 80 warnings (all `no-explicit-any`,
none on a line this branch added, by intersecting message lines with the
branch's added-line set); `--print-config` shows empty `parserOptions`,
so there is no type-aware linting and the diff cannot move any untouched
file's verdict. Under `--no-inline-config` the same run shows 1 error,
the block-disabled `SpecFormField` alias in `index.ts` that is on `main`
and outside this diff; the package `lint` scripts are `eslint .`, which
honours the disable. Full `pnpm lint` is left to CI.
- CI on this head: `Inert vi.mock Specifier Check` is red on `main`
since `f9c06ef6a` (PR objectui#10729), filed as objectui#10732.
Confirmed locally in a no-install probe worktree at `5c61e524`: exactly
one hit,
`apps/console/src/__tests__/filterContextTokensSweep-10666.test.tsx`,
outside this surface; in an installed worktree the specifier is found
through `node_modules` and the checker passes. Not touched here.

## Red on base, then green on head

The five touched sources (`data-display.ts`, `index.ts`, `objectql.ts`,
`zod/objectql.zod.ts`, `ObjectPivotTable.tsx`) were swapped to their
`origin/main` `5c61e524` blobs with the HEAD pins in place, then
restored to HEAD (every blob equal to `HEAD`, `git diff HEAD` empty,
types dist rebuilt, markers back to 2 and 1).

- Types test program (`tsc -p tsconfig.test.json`): exit 2.
`TS2305`/`TS2724` on the two new type names imported by the per-block
pin and the 6576 pin, and nine `TS2578 Unused '@ts-expect-error'` on the
per-block pin's refusals (the pivot's two, the table's seven).
- Vitest on the per-block pin and the 7352 mirror pin: `Tests 4 failed |
59 passed (63)`, the four failures being the zod describe's `filter`,
`maxRows`, `report` and `target` rows.
- Types dist built from base sources (markers 0 and 0), then the
plugin-dashboard test program: exit 2, `TS2305` on the 6576 anchor's
`ObjectDataTableDrillDownConfig` import and two `TS2578` on the pivot
pin.

## Ablations (each through `ablation-replace.mjs`: anchor hit once, blob
changed, command run, restored to the HEAD blob with an empty `git diff
HEAD`)

- **TS wiring.** `ObjectDataTableSchema.drillDown` back to the shared
`DrillDownConfig`, with a JSDoc marker `ABLATED_10685_TS1` on the line.
Types program exit 2: `TS2344` on `_SchemaTakesTheTableShape`, `TS2578`
on `nodeWithFilter` and `nodeWithNavigate`, `TS2344` on the 6576 pin's
`assertionDrillDownDeclared`, plus `zod-mirror-parity.test.ts` going red
with `objectql.zod.ts#ObjectDataTableSchema` (the repo's TS/zod parity
pin sees the two doors disagree: a second, independent instrument). Dist
leg: types rebuilt, marker count in `dist/objectql.d.ts` 1,
plugin-dashboard program exit 2 (`TS2344` on
`ObjectDataTable.schemaAnchor-6576`'s `Equal`); after the restore
rebuild the marker count is 0 and `ObjectDataTableDrillDownConfig` is
back to 2. A first pass of this leg read the dist marker with the wrong
grep (tsc emits the `import()` type with single quotes) and got 0; the
downstream red was the same in both passes, and the marker form above is
the evidence.
- **Each table tombstone alone** (`never` to `DrillDownConfig['KEY']`):
`filter` gives `TS2578` on `withFilter` and `nodeWithFilter`; `maxRows`
on `withMaxRows`; `report` on `withReport`; `target` widened back to the
shared union gives `TS2578` on `withNavigate` and `nodeWithNavigate`.
Each also turns `zod-mirror-parity` red as above.
- **Pivot tombstone** neutralised (marker `ABLATED_10685_PIVOT`): types
program `TS2578` on `withMode` and `handedAcross`; dist marker 1;
plugin-dashboard program exit 2 with two `TS2578` on the pivot pin;
marker 0 after the restore rebuild.
- **Chart binding** (`ObjectChartSchema.drillDown` to the shared type):
`TS2578` on the chart pin's `withMode` and `withReport`, plus the 8885
pin's `TS2344` and `TS2578`.
- **Zod door** back to the shared mirror: `Tests 4 failed | 59 passed
(63)`, the four refusal rows. **Each zod member alone** (the tombstone
renamed away, or `target` widened to include `navigate`): exactly one
failure each, its own row, `62 passed`.

Direction: red as predicted in every leg, with one increase (the parity
pin) beyond the prediction.

## Serial

`origin/main` `5c61e524` and PR objectui#8941's head `bb7d5e718`
(`claude/pr-7058-lucide-react-1.41.0`) were fetched into private refs;
`git merge-tree --write-tree` exits 0 against both. PR objectui#10707
and PR objectui#10643 have merged; objectui#10657 has no open PR and no
branch (its test is on `main`). `main` had moved in three of this PR's
files (the `filter` docblocks in `objectql.ts`, the `masked` column in
`data-display.ts`, the `filter` describes in `objectql.zod.ts`), so
`main` was merged in (`430223947f`, a merge commit, no conflicts). In
round 3 `main` had moved again in one file (`index.ts`, objectstack-ai#10730's retired
export), so `origin/main` `41ae65b26` was merged in the same way
(`4f2f7a530f`, no conflicts). At head `64d43b6d67`, `merge-tree
--write-tree` exits 0 against `origin/main` `41ae65b26` and against the
three open PRs that touch these files: PR objectui#10734 (`856ecc953`,
`ObjectChartSchema` regions of `objectql.ts` / `objectql.zod.ts`,
disjoint), PR objectui#10714 (`218f642484`, `data-display.ts`) and PR
objectui#8941 (`bb7d5e718`, `objectql.ts`). The branch diff against
`main` is exactly the 14 files of this card.

## Acceptance notes

- **`mode: 'filter'` on `object-data-table`** turns the row drill off,
as `ObjectDataTable.drill.test.tsx` pins, instead of drilling through.
The rewritten docblock says so. Value-level, outside the card's member
set; recorded here only.
- **`maxRows`** reaches each drill list as the drill table's `pageSize`
(`DrillDownDrawer` and the chart's drawer), a page size rather than the
"Hard cap on rows fetched" the shared docblock promises. Observation,
outside this card.
- **`object-chart`, non-fresh values.** A value typed as the shared
`DrillDownConfig` is still assignable to `ObjectChartSchema.drillDown`,
because the spec type omits `mode` and `report` rather than tombstoning
them. No producer in the tree hands a typed shared config across.
- **Spec guidance text.** `@objectstack/spec`'s `ChartDrillDownSchema`
refusal text for `mode` calls it "a TABLE / PIVOT / METRIC drill key";
on `object-pivot` and `object-metric` it is now refused. Reported to the
seat in round 1 for the spec repository; not touched here.
- **PR assignee.** Set to `os-elon-musk` in round 1 (read back
matching); found empty at the start of this round and set again to match
the card's assignee, per the dispatch rule.

Session: `https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN`

---
_Generated by [Claude
Code](https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

1 participant