Skip to content

test(ui): relax type-level restatements of spec literal unions; keep the two exhaustive pins on their consumers - #26328

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-6ol3w0
Oct 26, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-6ol3w0

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #26275

Clause-②: no
Metadata-protocol: none

What

Type Check UI went red on every ui PR whenever @objectstack/spec added a member to a union that a ui test pinned exactly with Equal<…>, even when no ui behaviour changed. This PR measures every such pin, decides each one, and relaxes the restatements. The pins over a union that ui is genuinely total over stay exact, and their red now names the ui site that needs the new case.

The surface is test files only: 14 files under ui/packages/**/__tests__ or *.test.ts(x). No source file, sdui record or published file changes, so it carries skip-changeset.

Population: 85, not 51

The card's figure (51 pins in 31 files at df8aac41f) came from a scanner whose list was not published. I re-measured at main 96635f57 with a TypeScript AST scan. It counts every Equal-family assertion (Equal, IsEqual, Equals, TypeEqual, Eq, Same, Exact, SameType, SameKeys, DeclaresExactly, including file-local aliases) where one argument is a literal union. That gives 85 candidates in 56 files, including the card's example (PendingActionStatus in plugin-chatbot spec-symbol-batch6.test.ts). The scan was rerun after #26272 merged and found the same set.

A syntactic scan cannot tell which unions the spec owns, so each verdict comes from an experiment, not from reading. I mutated the built spec declarations (packages/spec/dist/**/*.d.ts and .d.mts, which ui test configs resolve through paths: {}) in three ways:

  1. members: a new member on every z.ZodEnum, literal ZodUnion and quoted literal union;
  2. keys: a new key on every z.ZodObject and interface;
  3. tuples: a new member on every literal tuple.

I then ran tsc --noEmit and tsc -p tsconfig.test.json on all 10 packages that host a candidate. A pin that stays green under all three probes is one that a spec addition cannot move, so it was not part of the problem.

Verdicts

Verdict Count
restatement → relaxed 12
exhaustive consumer → kept exact, red points at the consumer 2
kept: a measured drift ledger, not a restatement 1
kept: probe artifact (the probe desynchronises a tuple from its enum) 1
kept: green under every probe 69

Relaxation form. A relaxed pin becomes Equal<Exclude<'a' | 'b' | …, U>, never>, so every member ui reads is still required. Where the old pin also guarded against widening, it is paired with Equal<string extends U ? true : false, false> (or the file's existing "is not string" pin), so the union cannot quietly widen to string. No assertion about a member ui reads was weakened.

The two exhaustive pins stay exact:

  • _ReasonIsTheEnum (DroppedFieldsEvent['reason'], data-objectstack spec-symbol-batch6.test.ts). Its consumer is STRIPPED_LINE in app-shell writeWarningToast.ts, a Record<DroppedFieldsEvent['reason'], StrippedLine>.
  • _VisualizationUnionIsTheNineDrawnKinds (core normalize-list-view.undrawableBothLegs-9880.test.ts). Its consumer is LIST_VIEW_KINDS in normalize-list-view.ts, a total Record, plus ListView's case list.

Both consumers are typed totally, so they go red on their own, and each pin's comment now names its consumer as the place to add the new case.

Full table

The Pin column is the alias name, or "(unnamed)" for an inline assertion. Files are under ui/packages/.

# File (under ui/packages) Pin Pinned type Red on a spec addition? Verdict Reason
1 app-shell/src/__tests__/spec-symbol-parity.test.ts _TypeIsClosed DecisionOutputDef['type'] yes restatement → relaxed re-export of the spec enum; nothing in ui is total over it. Now: not string + the five kinds present
2 app-shell/src/__tests__/spec-symbol-parity.test.ts _TwoMembersWide keyof TrackedObject no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
3 components/src/__tests__/filter-builder-name-authority-9306.test.tsx (unnamed) Exclude<FilterBuilderOperator, AuthorityCondition['operator']> yes kept (probe artifact) reds only because the probe desynchronises the spec's operator tuple from its enum; a real spec addition moves both together
4 components/src/__tests__/share-filter-sort-spec-parity.test.ts _RowKeys keyof FilterBuilderRow no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
5 core/src/registry/__tests__/component-config-single-declaration.test.ts registryOnlyKeys Exclude<keyof RegistryComponentConfig, keyof ComponentConfig> no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
6 core/src/registry/__tests__/component-meta-derives-from-canonical.test.ts registryOnlyKeys OnlyOnRegistry no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
7 core/src/utils/__tests__/normalize-list-view.undrawableBothLegs-9880.test.ts _DrawableUnderPublished Drawable<PublishedVocabulary, SimulatedTable> no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
8 core/src/utils/__tests__/normalize-list-view.undrawableBothLegs-9880.test.ts _DrawableUnderMain Drawable<MainVocabulary, SimulatedTable> no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
9 core/src/utils/__tests__/normalize-list-view.undrawableBothLegs-9880.test.ts _VisualizationUnionIsTheNineDrawnKinds ListViewVisualization yes exhaustive → kept exact LIST_VIEW_KINDS (normalize-list-view.ts) is a total Record and reds first; the pin now names it and ListView's case list
10 data-objectstack/src/metadata-client.draftHeaderMember-12413.types.test.ts _Keys keyof MetadataDraftHeader no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
11 data-objectstack/src/queryDataset.test.ts _DateGranularityEnum SelectionParam['dateGranularity'] yes restatement → relaxed no ui structure total over the buckets. Now: the five buckets present + not string
12 data-objectstack/src/spec-symbol-batch6.test.ts _ReasonIsTheEnum DroppedFieldsEvent['reason'] yes exhaustive → kept exact STRIPPED_LINE (app-shell writeWarningToast.ts) is a Record over the union; the pin now names it as the place a new arm is handled
13 data-objectstack/src/spec-symbol-batch6.test.ts _Shared Extract<keyof MetadataCacheStats, keyof SpecCacheStats> no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
14 data-objectstack/src/spec-symbol-batch6.test.ts _SpecOnly Exclude<keyof SpecCacheStats, keyof MetadataCacheStats> yes restatement → relaxed closed the spec's own key set. Now: keyCount and memoryUsage are spec-only
15 data-objectstack/src/spec-symbol-batch6.test.ts _LocalOnly Exclude<keyof MetadataCacheStats, keyof SpecCacheStats> no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
16 data-objectstack/src/spec-symbol-batch6.test.ts _HasDraftMode MetadataClientSaveOptions['mode'] no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
17 plugin-chatbot/src/__tests__/chat-message-contract.test.ts _RoleSurvives BarrelChatMessage['role'] no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
18 plugin-chatbot/src/__tests__/chat-message-contract.test.ts _AuthoringHasToolRole AuthoredChatMessage['role'] no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
19 plugin-chatbot/src/__tests__/chat-message-contract.test.ts _LegacyToolStatesAreAuthorable Extract< NonNullable<NonNullable<AuthoredChatMessage['toolInvocations']>[number]['state']>… no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
20 plugin-chatbot/src/__tests__/chat-message-contract.test.ts _KeepsToolRole ObjectChatMessage['role'] no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
21 plugin-chatbot/src/__tests__/chat-message-contract.test.ts _KeepsLegacyToolStates Extract< NonNullable<NonNullable<ObjectChatMessage['toolInvocations']>[number]['state']>, … no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
22 plugin-chatbot/src/__tests__/chat-message-contract.test.ts _IsTheApprovalTriple ChatbotRuntimeOnly no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
23 plugin-chatbot/src/__tests__/spec-symbol-batch6.test.ts _Vocabulary PendingActionStatus yes restatement → relaxed no inbox structure is total over the statuses. Now: the five statuses present; _StatusIsNotString keeps it closed
24 plugin-chatbot/src/__tests__/spec-symbol-batch6.test.ts _ApproveVocabulary ApproveOutcome['status'] yes restatement → relaxed the inbox compares against named statuses only. Now: executed/failed present (and rejected on the reject side)
25 plugin-detail/src/__tests__/ConcurrentUpdateDialog.narrowedCode-6421.test.tsx _CodeReadsAsMaybeUndefined typeof code no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
26 plugin-detail/src/renderers/__tests__/record-related-list.propsRefusal-9963.test.tsx _EqualRefusesAnIndexKeySet string | number no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
27 plugin-detail/src/renderers/__tests__/record-related-list.propsRefusal-9963.test.tsx _HostPropsAreTheOnesRead keyof RecordRelatedListRendererProps no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
28 plugin-form/src/__tests__/masterDetailDetailsMembers-8071.test.tsx memberList keyof MasterDetailDetailConfig yes restatement → relaxed derivedFromSpec already makes the config the spec entry. Now: the eleven members present
29 plugin-form/src/__tests__/objectFormLayout-11168.test.tsx _FormLayoutIsTwoValues FormSchema['layout'] no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
30 plugin-form/src/__tests__/parameterTypesNameable-7324.test.ts PIN keyof ChildObjectSchemaLike no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
31 plugin-form/src/__tests__/parameterTypesNameable-7324.test.ts PIN keyof DefaultsFieldValue no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
32 plugin-grid/src/__tests__/dataTableSchemaSlot-6459.test.ts _EveryHoldPinned keyof ObjectGridDataTableSchemaHolds no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
33 plugin-grid/src/__tests__/onNavigateProp-9547.test.tsx _TypeIsTheTwoMembers RecordNavigateAction no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
34 plugin-grid/src/hooks/useRecordCrudVerdicts.explainRequest.test.ts _StillExactlyTwoVerbs RecordCrudOperation no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
35 react/src/hooks/__tests__/navigationOverlayConsumers.onRowClickArity-9357.test.ts _WideAritySpansBoth Parameters<WideConsumer>['length'] no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
36 react/src/hooks/__tests__/offline-nav-performance-spec-parity.test.ts _StorageMembers NonNullable<OfflineCacheConfig['persistStorage']> no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
37 react/src/hooks/__tests__/offline-nav-performance-spec-parity.test.ts _OfflineKeys keyof OfflineConfig no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
38 react/src/hooks/__tests__/offline-nav-performance-spec-parity.test.ts _SyncKeys keyof OfflineSyncConfig no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
39 react/src/hooks/__tests__/useNavigationOverlay.onRowClickArity-9357.test.tsx _AritySpansBoth OnRowClickParams['length'] no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
40 types/src/__tests__/action-bar-chart-flag-11355.test.ts _ActionBarMembers keyof ActionBarSchema no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
41 types/src/__tests__/ai-zero-read-members-retired-8178.test.ts assertionLiveMembersKeepTheirTypes AIRecommendationsSchema['layout'] no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
42 types/src/__tests__/ai-zod-arms-10859.test.ts assertionMemberTypesAgree MismatchedKeys<typeof AIRecommendationsSchema, Ts_AIRecommendationsSchema> no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
43 types/src/__tests__/alert-dialog-read-dialect-7104.test.ts _ActionVariant AlertDialogSchema['actionVariant'] no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
44 types/src/__tests__/authoring-nodes-bag-carriers-11468.test.ts _ControlShapeOnlyLeavesTheOwnMemberBagsOpen OpenBagTypes<ShapeOnlyArmInput<(typeof ObjectQLPublicBlockComponentSchema)['options'][numb… no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
45 types/src/__tests__/chart-series-keys-7546.test.ts variant ChartDataSeries['variant'] no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
46 types/src/__tests__/chart-series-keys-7546.test.ts yAxis ChartDataSeries['yAxis'] no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
47 types/src/__tests__/chatbot-registration-authoring-faces-7655.test.ts assertionSharedKeyAliasIsTheCensus ChatbotSharedKey no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
48 types/src/__tests__/chatbot-registration-authoring-faces-7655.test.ts assertionSurfaceVocabulary ChatbotEnhancedSchema['surface'] no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
49 types/src/__tests__/dashboard-widget-slot-component-arm-7952.test.ts trend DashboardWidgetSlotComponentSchema['trend'] no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
50 types/src/__tests__/designer-ondelete-respelled-11434.test.ts assertionRespelled DataModelRelationship['deleteBehavior'] yes restatement → relaxed the row above already equates the face with the spec member. Now: the three actions present
51 types/src/__tests__/designer-ondelete-respelled-11434.test.ts assertionRespelled InputOf<ShapeOf<typeof DataModelRelationshipSchema>['deleteBehavior']> no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
52 types/src/__tests__/drill-down-config-declared-keys.test.ts _TargetUnion NonNullable<DrillDownConfig['target']> no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
53 types/src/__tests__/export-options-spec-parity.test.ts _ArrayArmIsCsvXlsxJson Extract< ListViewExportFace, unknown[] >[number] yes restatement → relaxed nothing in ui is total over the formats. Now: csv/xlsx/json present, pdf absent, not string
54 types/src/__tests__/filter-builder-mirror-6939.test.ts (unnamed) TsFilterField['type'] no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
55 types/src/__tests__/filter-builder-mirror-6939.test.ts (unnamed) TsFilterGroup['logic'] no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
56 types/src/__tests__/form-barrel-mirror-9406.test.ts type ShorthandFromRoot['type'] no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
57 types/src/__tests__/form-field-channel-keys-12130.test.ts assertionLocalMembers FormField['picker'] no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
58 types/src/__tests__/list-view-options-bag-6152.test.ts kinds keyof Bag yes restatement → relaxed the bag keys are the spec's. Now: the eight kinds present; closed keeps the no-index-signature half
59 types/src/__tests__/list-view-properties-bag-12670.test.ts assertionInstrumentFires SpecListView['type'] no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
60 types/src/__tests__/menu-item-union.test.ts _DividerKeySet keyof MenuDividerItem no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
61 types/src/__tests__/node-recursion-point-8344.test.ts NodeRecursionPointDeclarationDrift LiteralArmType< ArmsNotAssignableToSchemaNode > yes kept (not a restatement) a measured drift ledger of arms whose zod output is not a SchemaNode; red names a real TS/zod gap
62 types/src/__tests__/object-chart-properties-bag-11276.test.ts assertionInstrumentFires Exclude<keyof Mirror, NodeLevel> no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
63 types/src/__tests__/object-data-table-row-click-arity-9799.test.ts assertionAritySpansOneOrTwo Parameters<Slot>['length'] no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
64 types/src/__tests__/object-form-properties-bag-10859-b4.test.ts assertionTwinStaysPublished TsObjectFormSchema['mode'] no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
65 types/src/__tests__/objectql.exportOptions.test.ts _KeysAreExactlyTheSpecFive keyof ListViewExportOptions no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
66 types/src/__tests__/objectql.exportOptions.test.ts _FormatsAreCsvXlsxJson ListViewExportFormat no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
67 types/src/__tests__/page-kind-jsx-retired-12464.test.ts _KindIsTheSpecVocabulary NonNullable<PageNodeSchema['kind']> no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
68 types/src/__tests__/query-params-closed-keys-7497.test.ts _Closed keyof QueryParams no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
69 types/src/__tests__/record-highlights-fields-icon-9280.test.ts _EntryKeysMatchContract keyof DeclaredEntry no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
70 types/src/__tests__/report-chart-query-spec-parity.test.ts _blockTypeIsUnchanged BlockMember<PublishedJoinedReportBlock, 'type'> yes restatement → relaxed resolveReportPresentation falls back, nothing is total. Now: the three block types present, optional, not string
71 types/src/__tests__/report-schema-authoring-face.test.ts _SeriesTypeStaysThreeFamilies SeriesType no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
72 types/src/__tests__/select-option-form-view-boundary-12736.test.ts assertionObjectFieldOnlyKeysAreDefaultAndKind ObjectFieldOnlyKey no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
73 types/src/__tests__/select-option-form-view-boundary-12736.test.ts assertionBaseKeepsKindAndDefault Extract< keyof SelectOptionBase, 'kind' | 'default' > no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
74 types/src/__tests__/select-option-form-view-boundary-12736.test.ts assertionMetadataFaceKeepsKindAndDefault Extract< keyof SelectOptionMetadata, 'kind' | 'default' > no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
75 types/src/__tests__/sidebar-declared-surface-11465.test.ts assertionVariantIsTheRenderersEnum Ts_SidebarSchema['variant'] no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
76 types/src/__tests__/sidebar-declared-surface-11465.test.ts assertionVariantIsTheRenderersEnum InputOf<'variant'> no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
77 types/src/__tests__/spec-symbol-parity.test.ts _elementIsAuthoringOnly MirrorFilters['element'] no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
78 types/src/__tests__/strict-face-read-keys-11070.test.ts assertionGridFieldKeysOnTheFormFieldFace Extract<GridFieldOwnKeys, 'columns' | 'sortField' | 'sort_field' | 'name'> no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
79 types/src/__tests__/strict-face-read-keys-11070.test.ts assertionSidebarSideIsTheTwoEdges Ts_SidebarSchema['side'] no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
80 types/src/__tests__/strict-face-read-keys-11070.test.ts assertionSidebarSideIsTheTwoEdges SidebarSideInput no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
81 types/src/__tests__/undeclared-but-consumed-keys-6150.test.ts _OrientationIsAxis NonNullable< TsCarouselSchema['orientation'] > no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
82 types/src/__tests__/view-navigation-config-spec-parity.test.ts _KeysAreExactlyTheSpecSix keyof ViewNavigationConfig yes restatement → relaxed _IsExactlyTheSpecInput already refuses a local key. Now _KeysIncludeTheSpecSix + _ViewIsStillDeclared (the tombstone by name)
83 types/src/__tests__/view-navigation-config-spec-parity.test.ts _ModeMembershipIsTheSevenSpecModes NonNullable< ViewNavigationConfig['mode'] > yes restatement → relaxed no ui consumer is total over the modes. Now: the seven present + _ModeIsClosed
84 types/src/__tests__/zod-mirror-parity.test.ts assertionOpenRecordClauseIsAdditive WiderThanDeclaredKeys< SyntheticBothClassesMirror, SyntheticBothClassesDeclaration > no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)
85 types/src/__tests__/zod-mirror-parity.test.ts assertionAllowListReportsUnfiledAndCallbackRows { [K in keyof SyntheticAllowList]: | ${K}::${Exclude< SyntheticAllowKeys< K >, K extends …` no kept (probe green) green under all three probes, so a spec addition does not move it (ui-owned union, a ui narrowing, or both sides derived from the spec)

Same-family pins outside the literal-union scan

The probes also turned these red. They are exact pins on spec-derived key sets or entry shapes, not on literal unions, so they are the same class, and I relaxed them the same way:

File Pin Change
app-shell spec-symbol-parity.test.ts _BothRequired (FlowNodePosition) Pick<…, 'x' | 'y'> equals { x: number; y: number }: both coordinates the canvas reads stay required, and a key the spec adds to position is not red
app-shell spec-symbol-parity.test.ts _NoInventedKeys (ObjectFieldGroup) deleted: it closed the spec's own key list; _HasSpecKeys keeps the keys ui reads
app-shell spec-symbol-parity.test.ts _TypeIsClosed (DecisionOutputDef['type']) split into not-string plus _TypeKeepsTheFiveKinds (counted in the table)
data-objectstack spec-symbol-batch6.test.ts _SpecShape (SpecValidationError) field, message and code present
plugin-chatbot spec-symbol-batch6.test.ts _RejectVocabulary rejected present (a single literal, relaxed with its two siblings)
types select-option-tier1-convergence-7014.test.ts the form-face and metadata-face "kept every member / gained only …" pins every pre-convergence member kept, exactly (Pick equals the old shape); gained keys must be spec keys (assertion…GainedOnlySpecKeys), and the gains ui relies on (description; default and kind) are pinned by name
types widget-schema-anchors-7946.test.ts the aggregate groupBy pin split into: admits a field name; the structured node keeps field and optional alias; the five date granularities present
types field-metadata-depends-on-declared-6153.test.ts _SpecShapeIsArrayOfNameOrEntry split into: an array; admits a bare name; the entry keeps field and optional param

Reds that stay, and why

The keys probe turns many other ui type checks red. They fall into three groups, and none are restatements:

  • Derived parity, where both sides are computed from the spec: _FaceIsTheSpecInput, _InputEqualsParsed, the zod-mirror ledgers, spec-derived-unions, the record-details, record-highlights, gantt, conditional-formatting and object-chart pins, filter-operator-protocol-set, buildVerify-11988, share-filter-sort-spec-parity and designer-zod-arms. These go red only because the probe mutates one side of a pair that a real spec build keeps in step. This follows app-shell: the ObjectSchema corpus guards pin an exact top-level key count, so every additive spec change turns Test UI red on main; assert only the membership claims they guard #26238's ruling on derived pins.
  • Value-level totals in tests, where a test-local Record<SpecUnion, …> is a value, not a type pin: AppContent.recordDialogInterimBound-12521, identityImportWriteModes-12697 and AuditPanel.lockState. They are listed as an acceptance note below.
  • ui types narrower than the spec: MetadataService and friends, mostly TS2345 on payload objects. These are real ui gaps the probe surfaced, not test pins. They are out of this card's scope.

Proof

All runs happened on this head after merging main (#26272 included) and rebuilding the ui closure (75/75 turbo tasks), under scripts/pm/os-verify-lock.sh. Spec dist edits were backed up and restored with a hash check. The tree and dist were verified clean after each run.

  1. Baseline. All 10 packages, both configs: 0 errors.

  2. A single member on a restated union stays green. I added '__one26275' to PendingActionStatus only (contracts/index.d.ts and .d.mts). Result: 0 errors in all 19 type-check runs (data-objectstack has no test config). On main's version of the file, the full probe makes _Vocabulary red.

  3. A single member on an exhaustive union turns red at the consumer. I added __one26275 to DroppedFieldsEventSchema.reason only. Result: TS2741 at STRIPPED_LINE in app-shell writeWarningToast.ts, under both app-shell configs, and TS2344 at _ReasonIsTheEnum.These 3 are the only errors in all 19 runs.

  4. Full probe, head vs main. With all three probes applied, the only reds left in the 14 changed files are:

    • the two exhaustive pins, and their consumers STRIPPED_LINE and LIST_VIEW_KINDS;
    • _FaceIsTheSpecInput and _InputEqualsParsed (derived parity).

    Putting main's versions of the 14 files back under the same probe adds 22 reds, all on the pins this PR relaxes. Every red outside those files is the same in both runs.

  5. The relaxed pins are not vacuous. I injected a member that does not exist ('__absent26275', or an extra required key) into each of the 21 relaxed assertions. Result: exactly 21 errors, one per edit.

The probe mutates spec dist only. ui packages' own dist .d.ts files reference the spec rather than inlining it, so the mutation reaches them. Only the 10 packages that host a candidate were type-checked under the probes.

Acceptance notes

  • Value-level totals. Three tests hold a test-local Record<SpecUnion, …>: AppContent.recordDialogInterimBound-12521, identityImportWriteModes-12697 and AuditPanel.lockState. They are the runtime-shaped half of this class and will go red on a spec addition by design. Each is a single-line fix if a later card wants them on the Exclude form; I left them because they are values that tests iterate, not type pins.
  • Commit titles. The branch commits are titled WIP because they predate the merge of test(ui): assert what ui needs from spec-owned shapes, not their exact counts (#26238) #26272. Squash-merge yields one commit.

Gates

  • node scripts/pm/dispatch-gates.mjs --commands derived 24 families. I ran all 24, and every one exits 0. --ran reconciles them as 24 derived, 24 run, 0 NOT-MEASURED. check:type-check-debt ran under the verify lock: 26 raw errors, none above its recorded number, no surplus.
  • vitest run on the 14 changed files: 14 files and 350 tests pass.
  • Type Check UI shape (tsc --noEmit and tsc -p tsconfig.test.json) on all 10 host packages: 0 errors. Console build (turbo run build --filter=@object-ui/console): 71 of 71 tasks pass on the final merged head.

Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · seat domain:ui#1 · 2026-10-26T21:26Z

Head 147693d4 (read from the PR object).

  • Shape. Draft on main, Fixes #26275. 14 test files only, +190/-73, across types, app-shell, core, data-objectstack, plugin-chatbot and plugin-form. skip-changeset (no published source moves).
  • Checks pinned to the head. All check runs completed: 0 failing, 0 pending. The seat's landing script checked this before posting.
  • Governed surface. check-governed-merges.mjs --pr 26328: not governed.
  • Population. Re-measured with an AST scan at main 96635f57: 85 literal-union Equal-family pins in 56 files. This includes the card's example, and the PR body carries the full table. The card's 51 came from an unpublished scanner.
  • Verdicts, decided by mutating the built spec d.ts:
    • 12 restatements move to an Exclude-membership or Pick-on-own-keys form. The seat read the select-option-tier1-convergence-7014 rewrite: a local key still reds, and a key the spec adds no longer does.
    • 8 same-family key-set and entry-shape pins that went red under the probes are relaxed the same way.
    • 2 exhaustive pins stay exact, and their comments name the consumer that must change: DroppedFieldsEvent reason (STRIPPED_LINE) and ListViewVisualization (LIST_VIEW_KINDS).
    • 71 are kept, each with a reason.
  • Evidence (dev report 6427439948).
    • Adding a member to PendingActionStatus gives 0 errors across 19 tsc runs.
    • Adding one to the DroppedFieldsEvent reason gives exactly 3 errors, at STRIPPED_LINE and its pin.
    • Non-vacuity: 21 injected members produce 21 errors.
    • vitest 14 files / 350 tests; tsc on 10 packages, 0 errors; 24/24 gates, 0 NOT-MEASURED; Console build 71/71.
  • Serial constraints. Test files only: no sdui record covers them, and no other open PR of this seat edits them. The ⛔ paths are untouched.

Landing: ready + auto-merge through the relay.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 26, 2026 21:28
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 26, 2026 21:28
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 26, 2026
Merged via the queue into main with commit da99a4b Oct 26, 2026
60 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-6ol3w0 branch October 26, 2026 21:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ui tests: 51 type-level Equal pins on spec literal unions turn Type Check UI red when the spec adds a member

2 participants