From 0b2ae8d09154b0e396541743520a586b4b0475d7 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 30 Jul 2026 10:53:16 +0000 Subject: [PATCH] =?UTF-8?q?refactor(types)!:=20authoring=20types=20are=20i?= =?UTF-8?q?nput=20types=20=E2=80=94=20ActionParam=20takes=20the=20spec's?= =?UTF-8?q?=20declaration=20forms,=20ListViewSchema=20stops=20promising=20?= =?UTF-8?q?phantom=20defaults=20(objectstack#4074)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three public types said something different from what the platform accepts, found by making packages/types' tests compile (#3009) and resolving the declared p1-spec-alignment.test.ts debt site-by-site. ActionParam is now the AUTHORING shape, aligned with the spec's input: name/label/type optional, field/objectOverride added. The spec's primary declaration form — a bare field reference inheriting label/type/validation/ options from an object field — was unrepresentable while all three were required. The RESOLVED shape the dialog consumes (after app-shell's resolveActionParams() inlines the reference) remains core's ActionParamDef, all three required: authoring and resolved are different types on purpose. label is the spec's I18nLabel by import — which the new compile-time guard promptly revealed to be aliased to plain string in the current spec, so no behavioural widening today; importing the alias means objectui tracks any future widening automatically. ListViewInferred is z.input, not z.infer. The spec sub-schemas flowing into the list-view surface (userActions, tabs→ViewTab, sharing) carry .default()s, so the inferred output type made userActions.refresh or a tab's pinned/visible REQUIRED — but nothing on the render path runs .parse(): normalizeListViewSchema deliberately applies no defaults ("an absent flag stays absent", its own suite). The output type rejected valid authored metadata while promising renderers defaults that never arrive; input matches both author and runtime. FormField.dependsOn is DependsOnInput. The runtime reader (resolveCascadingOptions) has always accepted a bare name, a list of names, or {field, param} entries; the public property said string, so array-authored metadata type-errored while working and the form renderer read the key through (f as any).dependsOn to get past its own type. The shape now lives in @object-ui/types next to FormField; core imports and re-exports it; the two as-any reads in components are typed. The p1-spec-alignment.test.ts exclusion is gone — its 14 errors resolved: the "sharing in ObjectUI format" and legacy-ARIA fixtures are re-seated as normalizer-input dialects (folded by normalizeListViewSchema; asserted branch-by-branch in core's normalize-list-view.test.ts, the seam where the fold runs), and the one fixture no surface ever admitted (an ObjectQL triplet as a spec ViewTab.filter) is corrected to the rule-object form. Every test file in @object-ui/types is compiled, no exclusions. Discrimination-checked: reverting ListViewInferred to z.infer, dependsOn to string, or ActionParam.name to required each fails compile in the now-compiled tests (TS2739 / TS2322 / TS2741); restored, all projects clean. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_014DBMo8gnFduzgpuSHd5Eje --- .changeset/authoring-types-are-input-types.md | 66 +++++++++++++++++++ .../components/src/renderers/form/form.tsx | 4 +- packages/core/src/evaluator/optionRules.ts | 15 +++-- .../src/__tests__/p1-spec-alignment.test.ts | 65 +++++++++--------- .../src/__tests__/spec-derived-unions.test.ts | 15 ++++- packages/types/src/form.ts | 31 ++++++++- packages/types/src/index.ts | 1 + packages/types/src/ui-action.ts | 46 +++++++++---- packages/types/src/zod/objectql.zod.ts | 17 ++++- packages/types/tsconfig.test.json | 30 +++------ 10 files changed, 207 insertions(+), 83 deletions(-) create mode 100644 .changeset/authoring-types-are-input-types.md diff --git a/.changeset/authoring-types-are-input-types.md b/.changeset/authoring-types-are-input-types.md new file mode 100644 index 0000000000..2811481b1b --- /dev/null +++ b/.changeset/authoring-types-are-input-types.md @@ -0,0 +1,66 @@ +--- +'@object-ui/types': major +'@object-ui/core': patch +'@object-ui/components': patch +--- + +**Authoring types are input types (framework#4074 steps 2–3): `ActionParam` takes the spec's declaration forms, `ListViewSchema` stops promising parse-output defaults, and `FormField.dependsOn` matches its runtime reader.** + +Three public types said something different from what the platform accepts. All +three divergences were found by making `packages/types`' tests compile (#3009) +and then resolving the declared `p1-spec-alignment.test.ts` debt site-by-site +instead of papering over it. + +**`ActionParam` is now the authoring shape, aligned with the spec's input.** +`name` / `label` / `type` become optional and `field` / `objectOverride` appear: +the spec's primary way to declare a param — a bare field reference that inherits +label/type/validation/options from an object field — was unrepresentable while +all three were required. The *resolved* shape the dialog consumes (after +app-shell's `resolveActionParams()` inlines the reference) remains +`@object-ui/core`'s `ActionParamDef`, with all three required. Authoring and +resolved are different types on purpose. `label` and option labels take the +spec's `I18nLabel` by import — which the new compile-time guard promptly +revealed to be aliased to plain `string` in the current spec (the per-locale +record is the separate `I18nObject`), so this is not a behavioural widening +today; importing the alias means objectui tracks any future widening +automatically. + +**Breaking:** code destructuring `param.name` / `param.label` / `param.type` as +guaranteed must now handle the field-backed form (or consume the resolved +`ActionParamDef` instead, which is what dialog-side code should be doing). + +**`ListViewInferred` is `z.input`, not `z.infer`.** The spec sub-schemas that +flow into the list-view surface (`userActions`, `tabs` → `ViewTab`, `sharing`) +carry `.default()`s, so the inferred output type made fields like +`userActions.refresh` or a tab's `pinned`/`visible` *required* — but nothing on +the render path ever runs `.parse()`: `normalizeListViewSchema` deliberately +applies no defaults ("an absent flag stays absent", its own suite). The output +type therefore rejected valid authored metadata (`userActions: { sort: true }`) +while promising renderers defaults that never arrive. Typing the surface as +input matches both the author and the runtime object. Code that *trusted* those +phantom defaults now gets an optionality error — which is a latent bug surfacing, +not a regression: the value really could be absent. + +**`FormField.dependsOn` is `DependsOnInput`.** The runtime reader +(`resolveCascadingOptions`) has always accepted a bare name, a list of names, or +lookup-parameter entries `{ field, param }` — its parameter type says so. The +public property said `string`, so array-authored metadata type-errored while +working, and the form renderer read the key through `(f as any).dependsOn` to +get past its own type. The shape now lives in `@object-ui/types` (single source +of truth next to `FormField`), `@object-ui/core` imports and re-exports it, and +the two `as any` reads in the components form renderer are typed. + +**The `p1-spec-alignment.test.ts` exclusion is gone.** Its 14 errors resolved: +the two "sharing in ObjectUI format" tests and the legacy-ARIA-spelling fixture +are deleted/rewritten — those dialects are *normalizer input*, folded by +`normalizeListViewSchema` and asserted branch-by-branch in core's +`normalize-list-view.test.ts`, the seam where the fold actually runs; asserting +them on the canonical type only ever "passed" because nothing compiled the file. +One fixture claimed a shape no surface ever admitted (an ObjectQL triplet as a +spec `ViewTab.filter`) and was corrected to the rule-object form. Every test +file in `@object-ui/types` is now compiled, with no exclusions. + +Discrimination-checked: reverting `ListViewInferred` to `z.infer`, `dependsOn` +to `string`, or `ActionParam.name` to required each produces the expected +compile error in the now-compiled test files (`TS2739` / `TS2322` / `TS2741`); +restored, all projects are clean. diff --git a/packages/components/src/renderers/form/form.tsx b/packages/components/src/renderers/form/form.tsx index a469c19b7c..b1bd4a9fcf 100644 --- a/packages/components/src/renderers/form/form.tsx +++ b/packages/components/src/renderers/form/form.tsx @@ -591,7 +591,7 @@ ComponentRegistry.register('form', continue; const opts = (f as any).options as SelectOption[] | undefined; if (!opts || opts.length === 0) continue; - const dependsOn = (f as any).dependsOn; + const dependsOn = f.dependsOn; const hasOptionPredicate = opts.some((o) => (o as any)?.visibleWhen != null); if (!hasOptionPredicate && !dependsOn) continue; const current = form.getValues(name); @@ -1092,7 +1092,7 @@ ComponentRegistry.register('form', // `useCascadingOptions` hook (#2715). Non-option fields pass // their options through untouched. const cascade = isOptionField - ? resolveCascadingOptions(rawOptions, ruleRecord, (field as any).dependsOn, predicateScope) + ? resolveCascadingOptions(rawOptions, ruleRecord, field.dependsOn, predicateScope) : null; const optionGroupGated = cascade?.gated ?? false; const dependsOnFields = cascade?.dependsOnFields ?? []; diff --git a/packages/core/src/evaluator/optionRules.ts b/packages/core/src/evaluator/optionRules.ts index 6c92b2427e..046cd24d1c 100644 --- a/packages/core/src/evaluator/optionRules.ts +++ b/packages/core/src/evaluator/optionRules.ts @@ -29,6 +29,7 @@ * security boundary: when an option is gated for access-control reasons the * server must also reject writes of its value. */ +import type { DependsOnInput } from '@object-ui/types'; import { evalFieldPredicate, type FieldRulePredicate } from './fieldRules.js'; /** @@ -43,12 +44,14 @@ export interface OptionLike { visibleWhen?: FieldRulePredicate; } -/** A field's `dependsOn` as authored: a bare name, or a list of names / `{field,param}`. */ -export type DependsOnInput = - | string - | Array - | undefined - | null; +/** + * A field's `dependsOn` as authored — imported from `@object-ui/types`, where + * `FormField.dependsOn` is declared with it (framework#4074; formerly a local + * declaration that only the reader knew about, while the public + * `FormField.dependsOn` said `string`). Re-exported for this module's existing + * consumers. + */ +export type { DependsOnInput } from '@object-ui/types'; /** * Normalize a field's `dependsOn` to the list of sibling field names it reacts diff --git a/packages/types/src/__tests__/p1-spec-alignment.test.ts b/packages/types/src/__tests__/p1-spec-alignment.test.ts index 5973042c2a..49bd3b875d 100644 --- a/packages/types/src/__tests__/p1-spec-alignment.test.ts +++ b/packages/types/src/__tests__/p1-spec-alignment.test.ts @@ -111,12 +111,23 @@ describe('P1.1 ListView Spec Alignment', () => { }); it('should accept tabs configuration', () => { + // A minimal tab is valid AUTHORING input: the spec's ViewTab `.default()`s + // `pinned`/`visible`, so they are optional on the input side — which is what + // `ListViewSchema` types since framework#4074 (nothing on the render path + // parses, so defaults never materialize at runtime either). A tab filter is + // the spec's rule-object shape; the previous fixture wrote an ObjectQL + // triplet (`['owner', '=', 'current_user']`), which no type on this surface + // has ever admitted — this file just never compiled (objectui#3009). const schema: ListViewSchema = { type: 'list-view', objectName: 'Account', tabs: [ { name: 'all', label: 'All Records', isDefault: true }, - { name: 'mine', label: 'My Records', filter: ['owner', '=', 'current_user'] }, + { + name: 'mine', + label: 'My Records', + filter: [{ field: 'owner', operator: 'equals', value: 'current_user' }], + }, ], }; expect(schema.tabs).toHaveLength(2); @@ -170,35 +181,18 @@ describe('P1.1 ListView Spec Alignment', () => { expect(schema.sharing?.lockedBy).toBe('admin@example.com'); }); - it('should accept sharing in ObjectUI format { visibility, enabled }', () => { - const schema: ListViewSchema = { - type: 'list-view', - objectName: 'Account', - sharing: { - visibility: 'team', - enabled: true, - }, - }; - expect(schema.sharing?.visibility).toBe('team'); - expect(schema.sharing?.enabled).toBe(true); - }); - - it('should accept sharing with both spec and ObjectUI fields merged', () => { - const schema: ListViewSchema = { - type: 'list-view', - objectName: 'Account', - sharing: { - type: 'personal', - visibility: 'private', - enabled: true, - lockedBy: 'user@example.com', - }, - }; - expect(schema.sharing?.type).toBe('personal'); - expect(schema.sharing?.visibility).toBe('private'); - expect(schema.sharing?.enabled).toBe(true); - expect(schema.sharing?.lockedBy).toBe('user@example.com'); - }); + // (framework#4074) The two "sharing in ObjectUI format { visibility, enabled }" + // tests that sat here were deleted rather than made to pass. The legacy pair is + // real, but it is a NORMALIZER INPUT dialect, not part of `ListViewSchema`: + // `normalizeListViewSchema` (`@object-ui/core`) folds `visibility`/`enabled` + // onto the spec's `ViewSharing.type` at the ListView boundary, and ITS suite + // asserts every branch of that fold at the seam where it actually runs + // (`normalize-list-view.test.ts` — "collapses the visibility audience onto the + // spec ownership type", the bare-`enabled` mapping, `lockedBy` preservation, + // explicit-`type` precedence). Asserting the dialect on this type instead only + // ever "passed" because nothing compiled this file (objectui#3009) — and + // widening the canonical type to advertise a dialect that already has a fold + // would invite new metadata to author it. it('should accept exportOptions as spec string[] format', () => { const schema: ListViewSchema = { @@ -568,16 +562,21 @@ describe('P1.5 Record Components', () => { // ============================================================================ describe('P1.6 i18n & ARIA Protocol Alignment', () => { it('should accept ARIA props on ListViewSchema', () => { + // Canonical spellings: the spec's AriaProps (`ariaLabel`/`ariaDescribedBy`/ + // `role`) plus objectui's `live` extension. The legacy `label`/`describedBy` + // spellings this test used to author are a normalizer-input dialect — + // `normalizeListViewSchema` folds them onto the canonical keys, and core's + // suite asserts that fold at its own seam (framework#4074). const schema: ListViewSchema = { type: 'list-view', objectName: 'Account', aria: { - label: 'Accounts List', - describedBy: 'accounts-description', + ariaLabel: 'Accounts List', + ariaDescribedBy: 'accounts-description', live: 'polite', }, }; - expect(schema.aria?.label).toBe('Accounts List'); + expect(schema.aria?.ariaLabel).toBe('Accounts List'); expect(schema.aria?.live).toBe('polite'); }); diff --git a/packages/types/src/__tests__/spec-derived-unions.test.ts b/packages/types/src/__tests__/spec-derived-unions.test.ts index 1fb58bce76..f6b708369d 100644 --- a/packages/types/src/__tests__/spec-derived-unions.test.ts +++ b/packages/types/src/__tests__/spec-derived-unions.test.ts @@ -27,7 +27,7 @@ * implements it, so a host app typing against @object-ui/types got an error * on working code. * - * The `satisfies` checks below ARE enforcement — but only since #3005 added + * The `satisfies` checks below ARE enforcement — but only since #3009 added * `packages/types/tsconfig.test.json` and chained it from this package's * `type-check` script. * @@ -72,6 +72,7 @@ import type { ActionType, RunnableActionType, ActionComponent, + ActionParam, ActionParamFieldType, ResolvableParamFieldType, } from '../ui-action'; @@ -100,8 +101,20 @@ const _componentCovers = null as unknown as type SpecField = typeof SpecFieldType extends { options: readonly (infer T)[] } ? T : never; const _paramFieldCovers = null as unknown as SpecField satisfies ActionParamFieldType; const _resolvableCovers = null as unknown as ActionParamFieldType satisfies ResolvableParamFieldType; +// framework#4074 steps 2–3: `ActionParam` is the AUTHORING shape, aligned with +// the spec's input. The spec's primary declaration form — a bare field +// reference — must compile; it was a type error while `name`/`label`/`type` +// were required and `field` was undeclared. `label` is typed as the spec's own +// `I18nLabel` import — which THIS check revealed to be aliased to plain +// `string` in the current spec (the per-locale record is the separate +// `I18nObject`); importing the alias rather than restating it means objectui +// tracks any future widening automatically. (These annotations are enforcement +// since objectui#3009 made this file compile.) +const _fieldBackedParam: ActionParam = { field: 'status' }; +const _minimalTypedParam: ActionParam = { name: 'priority', label: 'Priority', type: 'select' }; void _chartCovers; void _reportCovers; void _actionCovers; void _pageCovers; void _vizCovers; void _runnableCovers; void _componentCovers; void _paramFieldCovers; void _resolvableCovers; +void _fieldBackedParam; void _minimalTypedParam; /** Read a spec enum's members, failing loudly if the shape ever changes. */ const optionsOf = (schema: unknown, name: string): string[] => { diff --git a/packages/types/src/form.ts b/packages/types/src/form.ts index 3af02eb5c2..da9412e838 100644 --- a/packages/types/src/form.ts +++ b/packages/types/src/form.ts @@ -854,6 +854,23 @@ export interface FormFieldPane { containerClass?: string; } +/** + * A field's `dependsOn` as authored: a bare parent-field name, or a list of + * names / lookup-parameter entries `{ field, param }`. + * + * This is the single source of truth for the shape (framework#4074). + * `@object-ui/core`'s `resolveDependsOnFields` / `resolveCascadingOptions` — + * the runtime readers — type their parameter with this import and re-export it, + * so the authoring surface and the reader can no longer disagree the way they + * did (the reader accepted arrays for as long as it has existed, while + * `FormField.dependsOn` said `string`). + */ +export type DependsOnInput = + | string + | Array + | undefined + | null; + /** * Form field configuration */ @@ -914,12 +931,20 @@ export interface FormField { */ widget?: string; /** - * Parent field name for cascading/dependent fields. + * Parent field(s) for cascading/dependent fields. * Aligns with @objectstack/spec FormField.dependsOn. - * When the parent field value changes, this field is re-evaluated. + * When a parent field value changes, this field is re-evaluated. + * + * The runtime reader (`resolveCascadingOptions` in `@object-ui/core`) has + * always accepted a bare name, a list of names, or lookup-parameter entries + * `{ field, param }` — its parameter type is exactly {@link DependsOnInput}. + * This property used to say `string` only, so array-authored metadata + * type-errored while working, and the form renderer read the key through + * `(f as any).dependsOn` to get past its own type (framework#4074). * @example 'country' (field 'state' dependsOn 'country') + * @example ['industry'] (multi-parent gating) */ - dependsOn?: string; + dependsOn?: DependsOnInput; /** * Visibility condition expression (view-level, from the form view's * FormField). Aligns with @objectstack/spec FormField.visibleOn — the wire diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 8ce7ea2d47..825f3eda32 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -134,6 +134,7 @@ export type { CalendarSchema, ValidationRule, FieldCondition, + DependsOnInput, FormField, FormFieldTab, FormFieldPane, diff --git a/packages/types/src/ui-action.ts b/packages/types/src/ui-action.ts index 3b9d8c8b9a..796e8b397b 100644 --- a/packages/types/src/ui-action.ts +++ b/packages/types/src/ui-action.ts @@ -23,6 +23,7 @@ import type { Action as SpecAction, ActionLocation, ActionType as SpecActionType, + I18nLabel, } from '@objectstack/spec/ui'; import { FieldType as SpecFieldTypeEnum } from '@objectstack/spec/data'; import type { FieldType as SpecFieldType } from '@objectstack/spec/data'; @@ -187,34 +188,53 @@ export type ResolvableParamFieldType = ActionParamFieldType | ObjectUiLocalParam * `paramToField.ts` maps into the shared field renderer, ADR-0059) was * unrepresentable in the public type while being fully implemented. * - * Still narrower than the spec's `ActionParamSchema` in two ways, deliberately - * left for a follow-up because both are breaking: `name` / `label` / `type` are - * required here but optional in spec, and the `field` / `objectOverride` - * field-reference form (spec's primary way to declare a param, which supplies - * label/type/validation/options from an existing object field) is absent. See - * #4074 steps 2–3. + * This is the AUTHORING shape, aligned with the spec's `ActionParamSchema` + * input (#4074 steps 2–3): `name` / `label` / `type` are optional because the + * `field` reference form supplies them from an existing object field, and + * labels take the spec's `I18nLabel` (a string or a per-locale record). The + * RESOLVED shape the dialog consumes — after `resolveActionParams()` in + * `@object-ui/app-shell` inlines the field reference — is `@object-ui/core`'s + * `ActionParamDef`, which keeps `name`/`label`/`type` required. Authoring and + * resolved are different types on purpose; conflating them is what made a + * spec-valid `{ field: 'status' }` param a type error here for as long as this + * interface required all three. */ export interface ActionParam { - /** Parameter name (snake_case) */ - name: string; + /** + * Parameter name (snake_case) — the request-body key. Optional: defaults to + * {@link field} when the param is field-backed. + */ + name?: string; - /** Display label */ - label: string; + /** + * Reference an existing object field to inherit its label / type / + * validation / options (the spec's primary way to declare a param). The + * resolver inlines the referenced config; explicit properties here override + * the inherited ones. + */ + field?: string; + + /** Object that owns {@link field} (defaults to the action's parent object). */ + objectOverride?: string; + + /** Display label. Overrides the resolved field label for field-backed params. */ + label?: I18nLabel; /** - * Field type for input. + * Field type for input. Optional: field-backed params inherit the referenced + * field's type. * * Accepts the spec vocabulary plus objectui's declared legacy spellings, which * is what the dialog resolves — prefer a canonical * {@link ActionParamFieldType} in new metadata. */ - type: ResolvableParamFieldType; + type?: ResolvableParamFieldType; /** Whether parameter is required */ required?: boolean; /** Options for select/picklist types */ - options?: Array<{ label: string; value: string }>; + options?: Array<{ label: I18nLabel; value: string }>; /** Default value */ defaultValue?: unknown; diff --git a/packages/types/src/zod/objectql.zod.ts b/packages/types/src/zod/objectql.zod.ts index 9d01d8eab7..11a234c803 100644 --- a/packages/types/src/zod/objectql.zod.ts +++ b/packages/types/src/zod/objectql.zod.ts @@ -490,11 +490,22 @@ export const ListViewSchema = BaseSchema }); /** - * Inferred TS type for the ListView component node (spec-derived; issue #2231). + * TS type for the ListView component node (spec-derived; issue #2231). * The hand-written `interface ListViewSchema` in `../objectql.ts` is now an alias of - * this inferred type intersected with the non-serializable runtime-only props. + * this type intersected with the non-serializable runtime-only props. + * + * `z.input`, not `z.infer` (framework#4074): this type describes the SDUI JSON as + * AUTHORED and as the renderer actually RECEIVES it. The spec sub-schemas that flow + * in (`userActions`, `tabs`→`ViewTab`, `sharing`, …) carry `.default()`s, so their + * `z.infer` output makes those fields required — but nothing on the render path + * runs `.parse()` to apply them: `normalizeListViewSchema` (`@object-ui/core`) + * deliberately applies no defaults ("an absent flag stays absent", its test suite). + * Typing the surface as parsed output therefore rejected valid authored metadata + * (`userActions: { sort: true }`, a tab without `pinned`/`visible`) while promising + * renderers defaults that never arrive. The output type of a spec parse belongs to + * whoever actually parses; this surface is input on both sides. */ -export type ListViewInferred = z.infer; +export type ListViewInferred = z.input; /** * ObjectMap Schema diff --git a/packages/types/tsconfig.test.json b/packages/types/tsconfig.test.json index 0275bc8b13..3d35df8cc3 100644 --- a/packages/types/tsconfig.test.json +++ b/packages/types/tsconfig.test.json @@ -27,26 +27,12 @@ // so @objectstack/spec resolves through the workspace dependency. "paths": {} }, - "include": ["src/**/*.test.ts", "src/**/*.test.tsx"], - // ── Known gap, declared rather than silent ───────────────────────────────── - // `p1-spec-alignment.test.ts` has 14 type errors across 8 sites, and they are - // not hygiene: every one sits in a test named "should accept " whose - // whole purpose is to assert the type ACCEPTS that shape. The type rejects it, - // and the test passed anyway because nothing compiled the file. Two examples: - // - // - "should accept sharing in ObjectUI format { visibility, enabled }" — - // `ListViewSchema.sharing` is `{ type, lockedBy }` only. The legacy shape is - // real and IS handled, by `foldSharing` in core's `normalize-list-view.ts`, - // but as untyped input (`normalizeListViewSchema(schema: T): T`), so no - // type names it. - // - several minimal fixtures assigned to output types whose fields carry - // `.default()`, the same input/output confusion fixed in - // `p2-spec-exports.test.ts` in this change. - // - // Each one is a separate decision — widen the type so the test's claim becomes - // true, or delete the claim — and several touch the public surface. Doing that - // silently while landing the type-check plumbing would bury it, so the file is - // excluded here WITH this reason and tracked in objectstack#4074. Removing this - // entry is the definition of done; it must only ever shrink. - "exclude": ["src/__tests__/p1-spec-alignment.test.ts"] + "include": ["src/**/*.test.ts", "src/**/*.test.tsx"] + // The `p1-spec-alignment.test.ts` exclusion that sat here (objectui#3009's + // declared debt) is gone: its 14 errors were resolved site-by-site in + // framework#4074 — `ListViewInferred` is `z.input` (authoring surface), + // `FormField.dependsOn` matches its runtime reader, the normalizer-input + // dialects are asserted at core's fold seam instead of on this type, and the + // one fixture no surface ever admitted (an ObjectQL triplet as a tab filter) + // was corrected. Every test file in this package is compiled; keep it that way. }