From 6f1bda122bebb712feff1df8989748449a11605e Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 26 Oct 2026 19:22:57 +0000 Subject: [PATCH 1/2] test(ui): relax type-level restatements of spec unions to membership (WIP) Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_01Ar9SeLTMJHQukiSgeSM45R --- .../src/__tests__/spec-symbol-parity.test.ts | 29 +++++---- ...-list-view.undrawableBothLegs-9880.test.ts | 6 ++ .../data-objectstack/src/queryDataset.test.ts | 10 +++- .../src/spec-symbol-batch6.test.ts | 16 ++++- .../src/__tests__/spec-symbol-batch6.test.ts | 13 ++-- .../masterDetailDetailsMembers-8071.test.tsx | 35 ++++++----- .../designer-ondelete-respelled-11434.test.ts | 7 ++- ...-metadata-depends-on-declared-6153.test.ts | 16 +++-- .../report-chart-query-spec-parity.test.ts | 9 ++- ...lect-option-tier1-convergence-7014.test.ts | 59 ++++++++++++++----- ...view-navigation-config-spec-parity.test.ts | 31 +++++++--- .../widget-schema-anchors-7946.test.ts | 11 +++- 12 files changed, 174 insertions(+), 68 deletions(-) diff --git a/ui/packages/app-shell/src/__tests__/spec-symbol-parity.test.ts b/ui/packages/app-shell/src/__tests__/spec-symbol-parity.test.ts index eee08d08afe..b82ca457318 100644 --- a/ui/packages/app-shell/src/__tests__/spec-symbol-parity.test.ts +++ b/ui/packages/app-shell/src/__tests__/spec-symbol-parity.test.ts @@ -413,9 +413,12 @@ describe('DecisionOutputDef is the spec type, with no local divergence left', () type _IsExactlyTheSpecType = Assert>; // Deriving NARROWED `type` from the bare `string` this file used to declare - // to the spec's closed enum — that narrowing is the point, so pin it. - type _TypeIsClosed = Assert< - Equal + // to the spec's closed enum — that narrowing is the point, so pin it: not + // `string`, and the five kinds still in it. Nothing in ui switches over + // every kind, so a kind the spec adds is the spec's to add, not a red here. + type _TypeIsClosed = Assert>; + type _TypeKeepsTheFiveKinds = Assert< + Equal, never> >; expect(true).toBe(true); @@ -447,8 +450,10 @@ describe('flow node geometry IS the spec `FlowNode.position` (#3172)', () => { type _InputEqualsParsed = Assert< Equal, NonNullable> >; - // Both coordinates required: a half-position is not representable. - type _BothRequired = Assert>; + // Both coordinates required: a half-position is not representable. Pinned + // on the two coordinates the canvas reads, so a key the spec adds to + // `position` is not a red here. + type _BothRequired = Assert, { x: number; y: number }>>; type _HalfIsNotAPosition = Assert, false>>; // …and the designer's node carries exactly that key, optional exactly as the @@ -488,22 +493,16 @@ describe('ObjectFieldGroup derives from the spec schema INPUT side', () => { // unrepresentable. If this flips, someone swapped z.input for z.infer. type _CollapseOptional = Assert>; - // Still the real spec vocabulary, not a hand copy that merely agrees. + // Still the real spec vocabulary, not a hand copy that merely agrees. The + // type is `z.input` of the spec schema itself, so the keys it carries are + // the spec's by construction: this names the ones the designer reads, and a + // key the spec adds is not a red here. type _HasSpecKeys = Assert< Extends< 'key' | 'label' | 'icon' | 'description' | 'visibleWhen' | 'collapse' | 'collapsible' | 'collapsed' | 'defaultExpanded', keyof ObjectFieldGroup > >; - type _NoInventedKeys = Assert< - Equal< - Exclude< - keyof ObjectFieldGroup, - 'key' | 'label' | 'icon' | 'description' | 'visibleWhen' | 'collapse' | 'collapsible' | 'collapsed' | 'defaultExpanded' - >, - never - > - >; expect(true).toBe(true); }); diff --git a/ui/packages/core/src/utils/__tests__/normalize-list-view.undrawableBothLegs-9880.test.ts b/ui/packages/core/src/utils/__tests__/normalize-list-view.undrawableBothLegs-9880.test.ts index 7ea6c79ce1e..09f3f980120 100644 --- a/ui/packages/core/src/utils/__tests__/normalize-list-view.undrawableBothLegs-9880.test.ts +++ b/ui/packages/core/src/utils/__tests__/normalize-list-view.undrawableBothLegs-9880.test.ts @@ -148,6 +148,12 @@ export const drawableUnderGrownOnceExplained: Record< * for — under the resolved spec AND under `main`'s. This is what makes the * change a re-spelling rather than a re-definition: the published type does not * move, only the way it is derived. + * + * Exact on purpose: ui draws this union exhaustively. A view kind the spec adds + * lands in `ListViewVisualization` unless `UndrawableViewKind` names it, and + * then `LIST_VIEW_KINDS` in `normalize-list-view.ts` (a total `Record` over the + * union) fails the build there first. That record and `ListView`'s `case` list + * are where the new kind is drawn; this list follows them. */ export type _VisualizationUnionIsTheNineDrawnKinds = Expect< Equal< diff --git a/ui/packages/data-objectstack/src/queryDataset.test.ts b/ui/packages/data-objectstack/src/queryDataset.test.ts index c6199ab41d8..d6b178383ee 100644 --- a/ui/packages/data-objectstack/src/queryDataset.test.ts +++ b/ui/packages/data-objectstack/src/queryDataset.test.ts @@ -449,12 +449,18 @@ describe('queryDataset(selection) is @objectstack/spec DatasetSelection itself', // month at all. A restatement does not only drift on the fields it has — it // also stops at whatever the contract looked like the day it was written. type _HasDateGranularity = Assert>; + // The same lesson holds for this pin, so it names the five buckets rather + // than restating the whole enum: a bucket the spec adds is not a red here, + // and a widening to `string` still is. type _DateGranularityEnum = Assert< Equal< - SelectionParam['dateGranularity'], - 'day' | 'week' | 'month' | 'quarter' | 'year' | undefined + Exclude<'day' | 'week' | 'month' | 'quarter' | 'year', SelectionParam['dateGranularity']>, + never > >; + type _DateGranularityIsClosed = Assert< + Equal + >; expect(true).toBe(true); }); diff --git a/ui/packages/data-objectstack/src/spec-symbol-batch6.test.ts b/ui/packages/data-objectstack/src/spec-symbol-batch6.test.ts index 331869ba3e7..cbc44858855 100644 --- a/ui/packages/data-objectstack/src/spec-symbol-batch6.test.ts +++ b/ui/packages/data-objectstack/src/spec-symbol-batch6.test.ts @@ -260,6 +260,12 @@ describe('DroppedFieldsEvent IS the spec type, not a mirror of it', () => { // and `main` agree again and this is the exact five-member union, with no // `Exclude`: any new arm is a remainder `Equal` refuses, and // `_ReasonIsNotString` below still refuses a widening to `string`. + // + // This pin stays exact on purpose: ui renders the union exhaustively. + // `STRIPPED_LINE` in `@object-ui/app-shell`'s `writeWarningToast.ts` is a + // `Record` over `DroppedFieldsEvent['reason']`, so a new arm also fails the + // app-shell build there. That map is where the new arm needs its wording + // (and both locale tables their row); this list follows it. type _ReasonIsTheEnum = Assert< Equal< DroppedFieldsEvent['reason'], @@ -281,7 +287,11 @@ describe('MetadataCacheStats is not the spec ICacheService stats', () => { // spec's is a server KV store sized in keys and bytes; this one is a bounded // browser LRU that evicts, coalesces in-flight fetches and reports a rate. type _Shared = Assert, 'hits' | 'misses'>>; - type _SpecOnly = Assert, 'keyCount' | 'memoryUsage'>>; + // The spec side is named by the two keys that make it a KV store, not + // closed: a key the spec adds to its own stats is not a red here. + type _SpecOnly = Assert< + Equal>, never> + >; type _LocalOnly = Assert< Equal< Exclude, @@ -319,7 +329,9 @@ describe('DataApiValidationError is an Error, the spec ValidationError is a reco // The spec's is one entry in a validation report. `@object-ui/types` // re-exports it under that name, so the two were importable side by side. - type _SpecShape = Assert, never>>; + // Named by the three members a report entry carries; a key the spec adds + // to it is not a red here. + type _SpecShape = Assert, never>>; // This one is a thrown runtime object: it has a `stack`, which no data // record does, and it carries the whole list the spec type is one item of. diff --git a/ui/packages/plugin-chatbot/src/__tests__/spec-symbol-batch6.test.ts b/ui/packages/plugin-chatbot/src/__tests__/spec-symbol-batch6.test.ts index 691a098d193..cbd374508e0 100644 --- a/ui/packages/plugin-chatbot/src/__tests__/spec-symbol-batch6.test.ts +++ b/ui/packages/plugin-chatbot/src/__tests__/spec-symbol-batch6.test.ts @@ -246,9 +246,12 @@ describe('the pending-action row and status ARE the spec contract', () => { type _NoCreatedAt = Assert>; type _NoUpdatedAt = Assert>; - // The vocabulary the inbox's tabs and badges are built on. + // The vocabulary the inbox's tabs and badges are built on: each status they + // name is still the spec's. No inbox structure is total over the union, so + // a status the spec adds is not a red here; `_StatusIsNotString` above + // still refuses a widening. type _Vocabulary = Assert< - Equal + Equal, never> >; expect(true).toBe(true); @@ -291,8 +294,10 @@ describe('the decision outcomes ARE the spec wire responses', () => { type _RejectStatusNotString = Assert< Equal >; - type _ApproveVocabulary = Assert>; - type _RejectVocabulary = Assert>; + // The statuses the inbox compares against are still on each response; a + // status the spec adds to either is not a red here. + type _ApproveVocabulary = Assert, never>>; + type _RejectVocabulary = Assert, never>>; // Drift 3 — `[k: string]: unknown` on the approve copy. objectstack#4075: // with it, this whole describe block would have been green on the copies. diff --git a/ui/packages/plugin-form/src/__tests__/masterDetailDetailsMembers-8071.test.tsx b/ui/packages/plugin-form/src/__tests__/masterDetailDetailsMembers-8071.test.tsx index 6cc2d8bafb7..0080d9e8d92 100644 --- a/ui/packages/plugin-form/src/__tests__/masterDetailDetailsMembers-8071.test.tsx +++ b/ui/packages/plugin-form/src/__tests__/masterDetailDetailsMembers-8071.test.tsx @@ -520,23 +520,28 @@ const derivedFromSpec: Equal[number], GridColumn> = true; /** - * Every member a detail can be authored with, spelled out: the spec entry's - * keys minus `sortField` (objectui#11070 round 9). A key the spec adds is a - * red row here, never a silent widening of what this block is said to read. + * Every member a detail is authored with today, spelled out: the spec entry's + * keys minus `sortField` (objectui#11070 round 9). Each must stay on the + * config. The list is not closed: `derivedFromSpec` above already makes the + * config the spec's entry, so a closed list would only restate the spec, and a + * key the spec adds would be a red row here with no ui change behind it. */ const memberList: Equal< - keyof MasterDetailDetailConfig, - | 'childObject' - | 'relationshipField' - | 'columns' - | 'formFields' - | 'inlineMode' - | 'amountField' - | 'totalField' - | 'title' - | 'minRows' - | 'maxRows' - | 'addLabel' + Exclude< + | 'childObject' + | 'relationshipField' + | 'columns' + | 'formFields' + | 'inlineMode' + | 'amountField' + | 'totalField' + | 'title' + | 'minRows' + | 'maxRows' + | 'addLabel', + keyof MasterDetailDetailConfig + >, + never > = true; /** The lit control for the directive below: the same literal without the retired member compiles. */ diff --git a/ui/packages/types/src/__tests__/designer-ondelete-respelled-11434.test.ts b/ui/packages/types/src/__tests__/designer-ondelete-respelled-11434.test.ts index bd0a4f9aa2a..21214dd85cf 100644 --- a/ui/packages/types/src/__tests__/designer-ondelete-respelled-11434.test.ts +++ b/ui/packages/types/src/__tests__/designer-ondelete-respelled-11434.test.ts @@ -55,10 +55,15 @@ type InputOf = T extends z.ZodType ? z.input : never; * `deleteBehavior` carries the spec's vocabulary on both faces, and `onDelete` * reads as `undefined` on both — a `?: never` tombstone, and an alias refusal * whose `z.input` is `undefined`. + * + * The TypeScript face IS the spec's member (first row), so the second row names + * the three actions rather than restating the spec's enum: an action the spec + * adds is not a red here. The zod face's enum is this package's own list, so + * its row stays exact, and `zod-mirror-parity.test.ts` holds it to the spec's. */ export type assertionRespelled = [ Expect>, - Expect>, + Expect, never>>, Expect['deleteBehavior']>, 'set_null' | 'cascade' | 'restrict' | undefined>>, Expect>, Expect['onDelete']>, undefined>>, diff --git a/ui/packages/types/src/__tests__/field-metadata-depends-on-declared-6153.test.ts b/ui/packages/types/src/__tests__/field-metadata-depends-on-declared-6153.test.ts index d67dde64516..d9fec360168 100644 --- a/ui/packages/types/src/__tests__/field-metadata-depends-on-declared-6153.test.ts +++ b/ui/packages/types/src/__tests__/field-metadata-depends-on-declared-6153.test.ts @@ -61,10 +61,18 @@ type Expect< T extends true > = T; /* ── 1. The declared member, in the spec's shape ─────────────────────────── */ export type _DependsOnIsDeclaredInSpecShape = Expect< Equal< NonNullable< BaseFieldMetadata['dependsOn'] >, FieldDependsOn > >; -// The derived shape is exactly what the installed spec declares at field level -// (`z.array(z.union([z.string(), z.strictObject({ field, param? })]))`) — if a -// spec release widens or narrows it, this line moves with it and says so. -export type _SpecShapeIsArrayOfNameOrEntry = Expect< Equal< FieldDependsOn, Array< string | { field: string; param?: string | undefined } > > >; +// The derived shape is what the installed spec declares at field level +// (`z.array(z.union([z.string(), z.strictObject({ field, param? })]))`): an +// array of a bare name or an entry with `field` and an optional `param`. Pinned +// on those members, so a narrowing is red here and a key the spec adds to the +// entry is not. +type DependsOnItem = FieldDependsOn extends Array< infer I > ? I : never; +export type _SpecShapeIsArray = Expect< Equal< FieldDependsOn, Array< DependsOnItem > > >; +export type _SpecShapeAdmitsAName = Expect< Equal< Extract< DependsOnItem, string >, string > >; +export type _SpecShapeIsArrayOfNameOrEntry = Expect< Equal< + Pick< Exclude< DependsOnItem, string >, 'field' | 'param' >, + { field: string; param?: string | undefined } +> >; // Inherited, not restated: the select and lookup faces carry the SAME member. export type _SelectInherits = Expect< Equal< SelectFieldMetadata['dependsOn'], BaseFieldMetadata['dependsOn'] > >; export type _LookupInherits = Expect< Equal< LookupFieldMetadata['dependsOn'], BaseFieldMetadata['dependsOn'] > >; diff --git a/ui/packages/types/src/__tests__/report-chart-query-spec-parity.test.ts b/ui/packages/types/src/__tests__/report-chart-query-spec-parity.test.ts index 81becc69085..2bc0ab3624e 100644 --- a/ui/packages/types/src/__tests__/report-chart-query-spec-parity.test.ts +++ b/ui/packages/types/src/__tests__/report-chart-query-spec-parity.test.ts @@ -923,6 +923,9 @@ const _blockDescriptionRefusesTheLegacyObjectArm = true satisfies ( : false ); // 5. UNCHANGED: `name` and `type` mean what the hand-written interface said. +// `type` is held to the three block types and its optionality, not closed: +// a block type the spec adds is not a red here (nothing in ui is total over +// this member; `resolveReportPresentation` falls back to `tabular`). const _blockNameIsUnchanged = true satisfies ( [BlockMember] extends [never] ? false @@ -931,7 +934,11 @@ const _blockNameIsUnchanged = true satisfies ( const _blockTypeIsUnchanged = true satisfies ( [BlockMember] extends [never] ? false - : SameType, 'tabular' | 'summary' | 'matrix' | undefined> + : ['tabular' | 'summary' | 'matrix' | undefined] extends [BlockMember] + ? [string] extends [BlockMember] + ? false + : true + : false ); void _specJoinedReportBlockTyping; void _specJoinedReportBlockIsNotEvenAny; diff --git a/ui/packages/types/src/__tests__/select-option-tier1-convergence-7014.test.ts b/ui/packages/types/src/__tests__/select-option-tier1-convergence-7014.test.ts index 3940e2b72a7..bc910cad54f 100644 --- a/ui/packages/types/src/__tests__/select-option-tier1-convergence-7014.test.ts +++ b/ui/packages/types/src/__tests__/select-option-tier1-convergence-7014.test.ts @@ -178,21 +178,38 @@ interface PRE_CONVERGENCE_SelectOptionMetadata { * spec's form-view option leaves it out and nothing on the form path read it — * not because the comparison was loosened: every OTHER member is still compared * invariantly. + * + * The face is read back on the record's own members (`Pick`), not with the + * spec's later additions subtracted by name: a key the spec adds to the option + * is not a narrowing, so it is not a red here. */ export type assertionFormFaceKeptEveryMember = - Expect< Equal< Omit< SelectOption, 'description' >, Omit< PRE_CONVERGENCE_SelectOption, 'default' > > >; + Expect< Equal< + Pick< SelectOption, Exclude< keyof PRE_CONVERGENCE_SelectOption, 'default' > >, + Omit< PRE_CONVERGENCE_SelectOption, 'default' > + > >; /** - * …and `description` is ALL it gained since. Written as a set difference so - * another key cannot ride along silently: add one and this stops being - * `'description'`. + * …and everything it gained since is the spec's: `description`, and no key of + * objectui's own. Written as a set difference against the spec's key set, so + * a local key cannot ride along silently, while a key the spec adds is not a + * red here. * * EDITED by objectui#12736: it was `'description' | SpecKindKey`. Spec 18's * `kind` no longer arrives on the FORM face — the spec's form-view option does - * not take it — so a `kind` that came back would turn this red. + * not take it — and `assertionFormFaceHasNoKindOrDefault` below turns red if + * it comes back. */ -export type assertionFormFaceGainedOnlyDescription = - Expect< Equal< Exclude< keyof SelectOption, keyof PRE_CONVERGENCE_SelectOption >, 'description' > >; +export type assertionFormFaceGainedOnlySpecKeys = + Expect< Equal< + Exclude< Exclude< keyof SelectOption, keyof PRE_CONVERGENCE_SelectOption >, keyof SpecSelectOption >, + never + > >; +export type assertionFormFaceGainedDescription = + Expect< Equal< + 'description' extends Exclude< keyof SelectOption, keyof PRE_CONVERGENCE_SelectOption > ? true : false, + true + > >; /** * …and the one member it lost is `default` (objectui#12736). Stated as its own @@ -207,19 +224,31 @@ export type assertionDescriptionIsOptional = /** * The object-metadata face kept every member it had, at the same type — the - * no-narrowing half, stated by removing the keys that were added (`default`, - * and `kind` where the spec declares it) and comparing what is left. + * no-narrowing half, stated by reading the face back on the record's own + * members, so a key the spec adds since is not a red here. */ export type assertionMetadataFaceKeptEveryMember = - Expect< Equal< Omit< SelectOptionMetadata, 'default' | SpecKindKey >, PRE_CONVERGENCE_SelectOptionMetadata > >; + Expect< Equal< + Pick< SelectOptionMetadata, keyof PRE_CONVERGENCE_SelectOptionMetadata >, + PRE_CONVERGENCE_SelectOptionMetadata + > >; /** - * …and `default` (plus spec 18's `kind`) is ALL it gained. Written as a set - * difference so another key cannot ride along silently: add one and this - * stops being `'default' | SpecKindKey`. + * …and everything it gained is the spec's: `default` (plus spec 18's `kind`) + * and no key of objectui's own. Written as a set difference against the + * spec's key set, so a local key cannot ride along silently, while a key the + * spec adds is not a red here. */ -export type assertionMetadataFaceGainedOnlyDefaultAndKind = - Expect< Equal< Exclude< keyof SelectOptionMetadata, keyof PRE_CONVERGENCE_SelectOptionMetadata >, 'default' | SpecKindKey > >; +export type assertionMetadataFaceGainedOnlySpecKeys = + Expect< Equal< + Exclude< Exclude< keyof SelectOptionMetadata, keyof PRE_CONVERGENCE_SelectOptionMetadata >, keyof SpecSelectOption >, + never + > >; +export type assertionMetadataFaceGainedDefaultAndKind = + Expect< Equal< + Exclude< 'default' | SpecKindKey, Exclude< keyof SelectOptionMetadata, keyof PRE_CONVERGENCE_SelectOptionMetadata > >, + never + > >; /** …and `default` really is optional, so no document that parsed before now fails. */ export type assertionDefaultIsOptional = diff --git a/ui/packages/types/src/__tests__/view-navigation-config-spec-parity.test.ts b/ui/packages/types/src/__tests__/view-navigation-config-spec-parity.test.ts index 12c859daedf..1a7b8ddb4d3 100644 --- a/ui/packages/types/src/__tests__/view-navigation-config-spec-parity.test.ts +++ b/ui/packages/types/src/__tests__/view-navigation-config-spec-parity.test.ts @@ -28,8 +28,8 @@ * tombstone KEEPS the key in the shape (`z.never().optional()`, so `z.input` * types it `undefined`, which is why an authored name reads as * `TS2322: Type 'string' is not assignable to type 'undefined'`) — so - * `SpecDeclaredKeys` below still lists `view`, and ⛔ removing it from that - * list would break `_KeysAreExactlyTheSpecSix`, not repair it. + * `SpecDeclaredKeys` below still lists `view`, and `_ViewIsStillDeclared` + * pins it by name: ⛔ removing it from that list would not repair anything. * * This is objectui#4550 / PR objectui#4586 one package over: that one collapsed * `@object-ui/react`'s `NavigationConfig` to the spec's authored input. This @@ -104,12 +104,17 @@ type _IsExactlyTheSpecInput = Expect< Equal< ViewNavigationConfig, NavigationCon * `retiredKey()` tombstone is `z.never().optional()`: the key is still IN the * object shape, so `keyof` still yields it — only its input type collapses from * `string | undefined` to `undefined`. DECLARED and WRITABLE are two different - * facts, and this alias measures the first one. Dropping `view` here reddens - * `_KeysAreExactlyTheSpecSix` against both the installed spec and spec@main. + * facts, and this alias measures the first one. `_ViewIsStillDeclared` below + * holds `view` by name, so it stays measured even if the list is tidied. + * + * Membership, not a closed list: `_IsExactlyTheSpecInput` above already refuses + * a local key, so closing this list again would only restate the spec, and a + * key the spec adds would turn this file red with no ui change behind it. */ type SpecDeclaredKeys = 'mode' | 'view' | 'preventNavigation' | 'openNewTab' | 'size' | 'width'; -type _KeysAreExactlyTheSpecSix = Expect< Equal< keyof ViewNavigationConfig, SpecDeclaredKeys > >; +type _KeysIncludeTheSpecSix = Expect< Equal< Exclude< SpecDeclaredKeys, keyof ViewNavigationConfig >, never > >; +type _ViewIsStillDeclared = Expect< Equal< 'view' extends keyof ViewNavigationConfig ? true : false, true > >; /* ── `mode` is optional, because the spec defaults it ────────────────────── */ @@ -129,13 +134,23 @@ type _ModeIsOptional = Expect< * puts there on purpose, not a drift in which modes exist. `NonNullable` keeps * this assertion pointed at the thing it guards. (Same care PR objectui#4586 * took with `_ModeIsConfigMode` one package over.) + * + * The seven are required members, not a closed list: no ui consumer is total + * over the modes, so a mode the spec adds is not a red here. A widening to + * `string` still is. */ type _ModeMembershipIsTheSevenSpecModes = Expect< Equal< - NonNullable< ViewNavigationConfig['mode'] >, - 'page' | 'drawer' | 'modal' | 'split' | 'popover' | 'new_window' | 'none' + Exclude< + 'page' | 'drawer' | 'modal' | 'split' | 'popover' | 'new_window' | 'none', + NonNullable< ViewNavigationConfig['mode'] > + >, + never > >; +type _ModeIsClosed = Expect< + Equal< string extends NonNullable< ViewNavigationConfig['mode'] > ? true : false, false > +>; /* ── The three schema sites use the shared type ──────────────────────────── */ @@ -195,7 +210,7 @@ describe('ViewNavigationConfig is the spec navigation config (objectui#4588)', ( // than an omission: objectstack#18619 retired it, so on a spec carrying the // tombstone its input type is `undefined` and any authored name is // `TS2322: Type 'string' is not assignable to type 'undefined'`. It is - // still a DECLARED key — `_KeysAreExactlyTheSpecSix` above measures that, + // still a DECLARED key — `_ViewIsStillDeclared` above measures that, // on this same file, which is the control that keeps this case's shorter // list from reading as "the spec lost a key". const full: ViewNavigationConfig = { diff --git a/ui/packages/types/src/__tests__/widget-schema-anchors-7946.test.ts b/ui/packages/types/src/__tests__/widget-schema-anchors-7946.test.ts index 2956c7eabdd..55a73f382b9 100644 --- a/ui/packages/types/src/__tests__/widget-schema-anchors-7946.test.ts +++ b/ui/packages/types/src/__tests__/widget-schema-anchors-7946.test.ts @@ -151,9 +151,18 @@ export type assertionAggregateFieldStaysOptional = * one the renderer's `aggregateGroupByKey` (`gb.alias || gb.field`) resolves. An * earlier draft declared this arm locally with `field?: string`, which published * a node that cannot resolve a category axis at all. + * + * Pinned on the members that renderer reads, not on the whole spec arm: a key + * or a granularity the spec adds to the structured node is not a red here. */ +type AggregateGroupBy = NonNullable['groupBy']>; +type StructuredGroupBy = Exclude; +export type assertionAggregateGroupByAdmitsAName = + Expect, string>>; export type assertionAggregateGroupByAdmitsStructuredNode = - Expect['groupBy']>, string | { field: string; dateGranularity?: 'day' | 'week' | 'month' | 'quarter' | 'year'; alias?: string }>>; + Expect, { field: string; alias?: string }>>; +export type assertionAggregateGroupByKeepsTheFiveGranularities = + Expect>, never>>; /** The helpers can FAIL — synthetic controls. */ export type assertionExtendsBaseCanFail = Expect, false>>; export type assertionEqualCanFail = Expect, false>>; From 06979e6f6c042a28b81e12d9bae434931805070e Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 26 Oct 2026 19:39:19 +0000 Subject: [PATCH 2/2] test(ui): relax the two export-format and options-bag restatements after the #26238 sweep (WIP) Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_01Ar9SeLTMJHQukiSgeSM45R --- .../__tests__/export-options-spec-parity.test.ts | 13 +++++++++---- .../__tests__/list-view-options-bag-6152.test.ts | 8 +++++++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/ui/packages/types/src/__tests__/export-options-spec-parity.test.ts b/ui/packages/types/src/__tests__/export-options-spec-parity.test.ts index a58ca6897f5..adf0dcddec5 100644 --- a/ui/packages/types/src/__tests__/export-options-spec-parity.test.ts +++ b/ui/packages/types/src/__tests__/export-options-spec-parity.test.ts @@ -222,10 +222,15 @@ type _FaceCarriesStreaming = Expect< Equal< Exclude< ListViewExportFace, unknown[] >['streaming'], boolean | undefined > >; -/** The array arm's vocabulary is the spec's three formats — `'pdf'` is not among them. */ -type _ArrayArmIsCsvXlsxJson = Expect< - Equal< Extract< ListViewExportFace, unknown[] >[number], 'csv' | 'xlsx' | 'json' > ->; +/** + * The array arm's vocabulary carries the spec's three formats — `'pdf'` is not + * among them, and it is not `string`. Membership, not a closed list: nothing in + * ui is total over the formats, so a format the spec adds is not a red here. + */ +type ArrayArmFormat = Extract< ListViewExportFace, unknown[] >[number]; +type _ArrayArmIsCsvXlsxJson = Expect< Equal< Exclude< 'csv' | 'xlsx' | 'json', ArrayArmFormat >, never > >; +type _ArrayArmHasNoPdf = Expect< Equal< 'pdf' extends ArrayArmFormat ? true : false, false > >; +type _ArrayArmIsClosed = Expect< Equal< string extends ArrayArmFormat ? true : false, false > >; /** A minimal, valid `list-view` node — the envelope every parse below rides on. */ const NODE = { type: 'list-view', objectName: 'accounts' } as const; diff --git a/ui/packages/types/src/__tests__/list-view-options-bag-6152.test.ts b/ui/packages/types/src/__tests__/list-view-options-bag-6152.test.ts index 3cce7d05e84..ca3f923c82f 100644 --- a/ui/packages/types/src/__tests__/list-view-options-bag-6152.test.ts +++ b/ui/packages/types/src/__tests__/list-view-options-bag-6152.test.ts @@ -305,8 +305,14 @@ type Equal = (() => T extends A ? 1 : 2) extends (() => T extends B describe('objectui#6152 round 12 — the TypeScript face is closed the same way', () => { it('the bag type has the eight kinds and no index signature', () => { + // `closed` is the no-index-signature half. The eight kinds are members, not + // a closed list: the bag's keys are the spec's, so a kind the spec adds is + // not a red here. const closed: string extends keyof Bag ? 'open' : 'closed' = 'closed'; - const kinds: Equal = true; + const kinds: Equal< + Exclude<'kanban' | 'calendar' | 'gallery' | 'timeline' | 'gantt' | 'map' | 'chart' | 'tree', keyof Bag>, + never + > = true; expect([closed, kinds]).toEqual(['closed', true]); });