Skip to content

Commit 1bbaa16

Browse files
feat(types): App/Dashboard/Page twins take the spec by reference, like their zod mirrors (#10273)
Fixes #9736 Clause-②: yes Executes ruling `5732099575` (batch #167 item 4, letter 甲; the maintainer agreed with 「其他同意」 at 2026-09-18T15:17Z). The three hand-written TypeScript twins now take the spec by reference, the same way their zod mirrors do. The exclusion list is hoisted to one `as const` array. The mirror's `specFieldsExcept` call and the twin both read that array. This is a types-only change: no runtime code changes and no mirror is narrowed. ## What changed | twin | extends | twin-only omission | |---|---|---| | `AppComponentSchema` (`app.ts`) | `BaseSchema`, plus Omit over spec `App` of `(typeof APP_SPEC_EXCLUDED)[number]` | none | | `DashboardComponentSchema` (`complex.ts`) | `BaseSchema`, plus Omit over spec `Dashboard` of `DASHBOARD_SPEC_EXCLUDED` plus `'header'` | `header` | | `PageNodeSchema` (`layout.ts`) | `BaseSchema`, plus Omit over spec `Page` of `PAGE_SPEC_EXCLUDED` plus `'slots'` plus `'assignedProfiles'` | `slots`, `assignedProfiles` | - **Hoist only.** In `zod/{app,complex,layout}.zod.ts`, each inline list became `export const X_SPEC_EXCLUDED = [...] as const`, and `specFieldsExcept` now reads that array. The members are byte-identical, so the mirror's accept set does not change. The arrays are not re-exported from `zod/index.zod.ts`, which uses named re-exports. The twins read them with `import type`. `zod-mirror-parity`'s closed-population census now lists them in `EXCLUSIONS`, because they are key-name tuples, not schemas. - **Twin-only omissions.** Only the compiler's collisions are omitted, and each one has a docblock line saying why. Dashboard `header` and Page `slots` are hand-written members that are **not assignable** to the spec's member. For `header`, `actionUrl` is optional where the spec requires it, `label` is `string` against `I18nLabel`, and `actionType` is an open string. For `slots`, `SchemaNode` admits `null` and primitives. Both are already ledgered in `KnownDrift` and `WiderThanDeclared`. Both are withheld on the type side only, and neither twin member was widened. The other twin-declared members (`icon`, `branding`, `active`, `hidden`, `requiredPermissions`, and the page's `object`, `template`, `variables`, `isDefault`, `aria`, `kind`) are assignable to the spec's member, so they override it without an omission. Page `assignedProfiles` is withheld the same way, for forward compatibility: objectstack `main` retires it (`retiredKey`), so the hand-written `string[]`, left unchanged, would fail the extends clause under the Spec Main Shape Gate. Its retirement is objectui#9409 (on hold). *(Added by the PM seat after commit `8e11943`.)* - **`contextSelectors`** is in `APP_SPEC_EXCLUDED` because the mirror re-adds it with its own element. The ruling names it among the admitted keys, so the App twin now declares `contextSelectors` as an optional array whose element is the `z.input` of the mirror's `AppContextSelectorSchema`. That takes the mirror's own element by reference, and the generic is spelled out in words here because GitHub strips angle-bracket shapes. Without that declaration the App row could not reach zero. - **Ledger.** The `MirroredUndeclared` rows for `app.zod.ts#AppComponentSchema` (20 keys), `complex.zod.ts#DashboardComponentSchema` (11) and `layout.zod.ts#PageNodeSchema` (10) are deleted. Each deletion leaves a note where its row stood. `assertionMirroredUndeclaredMatchesLedger` and `assertionMirroredUndeclaredRecordsEveryKey` still reconcile every pair, and the census sentence moves to the derived figures. No other ledger (`KnownDrift` / `UnmirroredDeclared` / `WiderThanDeclared`) moved. - **Pins.** `twins-spec-by-reference-9736.test.ts`: - For each twin, the set of spec keys minus declared keys is `never`, with lit and negative controls. - Admitted keys have the spec's own member type. This is checked with `Equal`, not by assignment, because `BaseSchema`'s index signature would make an assignment vacuous. - `@ts-expect-error` on the tombstones App `version` and Dashboard `refreshInterval`, plus the mirror's parse refusal on the same literal. - The `header` and `slots` members keep their own types. - `dashboard-aria-retired-contract-twins.test.ts` was rewritten. It pinned that `aria` is *not declared*. After this change `aria` is declared, but only as the spec tombstone typed `undefined`. The pin now asserts that, and it adds the `@ts-expect-error` that its old header said could not stick. ## Measured deviations from the dispatch's assumptions - Tombstones surface as `?: undefined` (the `z.input` of `z.never().optional()`), not as `?: never`. For the accept set under `exactOptionalPropertyTypes: false` they are equivalent: authoring a value is a compile error. - On `@objectstack/spec` 17.4.0, `Page` carries **no** tombstone. The Page pin therefore covers the admitted half only, and the test says so. ## Evidence All readings below were taken on the merged head `a1cc88e32`, except where a line says otherwise. The consumer type-check, the reverse verification and the check scripts ran on the pre-merge head `1d24a9ef9` plus the changeset commit. The merge brought in only the `objectql*` files from sibling work, and the `@object-ui/types` type-check and vitest were re-run after it. - `pnpm --filter @object-ui/types type-check` exited 0. It chains `tsc --noEmit`, then `tsconfig.examples.json`, then `tsconfig.test.json`. - `pnpm exec vitest run packages/types/ apps/console/src/__tests__/registry-inputs-spec-parity.test.ts` reported 215 files and 5126 tests passed, exit 0, on the merged head. - Consumer type-check (the downstream direction: every package whose source names one of the three interfaces) passed for all 10 packages: app-shell, components, core, layout, plugin-dashboard, plugin-designer, plugin-list, react, runner and example-schema-catalog. **Zero consumer fixes were needed.** This ran after a turbo build of their dependency closure (32 tasks). - Reverse verification that consumers read the rebuilt `.d.ts`: a probe file in `plugin-dashboard/src` authored `refreshInterval: 5` on a `DashboardComponentSchema`. `tsc` went red with `TS2322 Type 'number' is not assignable to type 'undefined'`. The probe was then removed and `git status` was clean. - eslint on the 9 changed `.ts` files: 0 errors. The 14 warnings are all pre-existing `no-explicit-any` hits on lines this diff does not touch. - `check-changeset-presence`, `check:spec-symbols`, `check:new-line-citations` (0 new), `check:control-bytes`, `check:installed-pin-claims`, `check:changeset-claims`, `check:pending-changeset-literals`, `check:published-dist`, `check:unreferenced-sources` and `check:published-tsconfig-exclude` all exited 0. ## Changeset: minor `.changeset/9736-twins-spec-by-reference.md` declares `@object-ui/types` as `minor`. The published type surface gains members. One authoring spelling (a tombstone key) moves from a parse-time refusal to a compile-time refusal, and this repo marks breaking semantics `minor`, never `major`. ## Acceptance notes - `check:changeset-claims` (report-only) lists 39 pending changesets that name files this diff touches. One of them now reads false: `.changeset/dashboard-aria-member-retired-5830.md` says `BaseSchema`'s index signature means an existing `aria:` line still compiles. After this PR it does not compile. That text is another change's pending declaration and is left untouched here. This PR's changeset states the new behaviour explicitly. Carrier: the release seat, or whoever next edits that changeset. - Serial siblings objectui#10084 and objectui#10066 and PR objectui#10260 touch disjoint files. `origin/main` was merged before this PR opened, and the gates above re-ran on the merged head. Session: `https://claude.ai/code/session_01877XiBYSaRCk2CU7cMSg3S` --- _Generated by [Claude Code](https://claude.ai/code/session_01877XiBYSaRCk2CU7cMSg3S)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 1dbb993 commit 1bbaa16

10 files changed

Lines changed: 411 additions & 99 deletions
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
'@object-ui/types': minor
3+
---
4+
5+
feat(types): `AppComponentSchema`, `DashboardComponentSchema` and `PageNodeSchema` take the spec by reference, like their zod mirrors
6+
7+
The three published zod mirrors have long taken `@objectstack/spec`'s `App` / `Dashboard` /
8+
`Page` surface by reference (`BaseSchema.extend(SpecXFields.shape)`), while their hand-written
9+
TypeScript twins restated only the members the renderers read. So the published validator
10+
admitted keys the published type did not declare: the package-lock envelope (`_lock`,
11+
`_lockReason`, `_lockSource`, `_provenance`, `_packageId`, `_packageVersion`, `_lockDocsUrl`,
12+
written by the packaging pipeline), `protection`, the app's `isDefault` / `_unpublished` /
13+
`defaultAgent` / `contextSelectors`, and the page's `source` / `interfaceConfig` / `requires`.
14+
The spec's retirement tombstones reached the type only as `any`, through `BaseSchema`'s index
15+
signature.
16+
17+
Each twin now extends `Omit` over the spec's exported input type, and that `Omit` reads the same
18+
`as const` exclusion array the mirror's `specFieldsExcept` call reads. The two faces therefore
19+
project one spec surface, and a later spec bump moves both of them together:
20+
21+
- **Admitted keys are declared** with the spec's own member types. A reader of a served document
22+
can read `app.protection`, `page.requires` or the envelope without a cast.
23+
- **Tombstones refuse at compile time.** `version`, `homePageId`, `objects`, `apis`, `sharing`,
24+
`embed`, `mobileNavigation` and `aria` on an app, and `aria`, `refreshInterval` and
25+
`performance` on a dashboard, are now optional members typed `undefined`. Authoring a value is
26+
a TypeScript error, which is the verdict the validator already gave at parse. ⚠️ This is the
27+
one breaking edge: code that wrote one of these keys used to compile against the index
28+
signature and was refused at parse. It is now refused by `tsc`, before it runs.
29+
- `DashboardComponentSchema.header` and `PageNodeSchema.slots` keep their own hand-written types.
30+
Both are withheld from the spec projection on the type side only, because neither type is
31+
assignable to the spec's member. The drift is already recorded in the parity ledgers.
32+
`PageNodeSchema.assignedProfiles` is withheld the same way, but for forward compatibility:
33+
objectstack `main` retires the key, and the hand-written `string[]` member (unchanged) would
34+
otherwise stop compiling at the next spec bump. Its retirement is tracked separately.
35+
36+
Why this is a minor bump: the published type surface gains members, and one authoring
37+
spelling moves from a parse-time refusal to a compile-time refusal. This repo marks breaking
38+
semantics `minor` rather than `major`.
39+
40+
This change affects types only. It changes no runtime code and narrows no mirror.

‎packages/types/src/__tests__/dashboard-aria-retired-contract-twins.test.ts‎

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,19 @@
2121
* `AriaPropsSchema`, the opposite of the contract: the member-level instance
2222
* of #4631's "declared surfaces disagree".
2323
*
24-
* What the deletion changes at the type level, stated honestly: `BaseSchema`
25-
* carries `[key: string]: any`, so an authored `aria:` on a dashboard literal
26-
* still COMPILES after the removal — it falls to the index signature. A
27-
* `@ts-expect-error` pin on an authored literal therefore cannot stick here
28-
* (unlike `default-children-retired-contract-twins.test.ts`, whose interface
29-
* has no index signature). The pinnable effect is that `aria` stops being a
30-
* DECLARED member: the probe below extracts the interface's literal key set —
31-
* the index signature is filtered out by `string extends K` — and asserts
32-
* `aria` is out while its former neighbours stay in. Real enforcement because
33-
* `packages/types/tsconfig.test.json` is chained from this package's
34-
* `type-check` script (#3009).
24+
* What the deletion changed at the type level, stated honestly: `BaseSchema`
25+
* carries `[key: string]: any`, so after the removal an authored `aria:` on a
26+
* dashboard literal still COMPILED — it fell to the index signature, and a
27+
* `@ts-expect-error` pin on an authored literal could not stick.
28+
*
29+
* objectui#9736 closed that half. The interface now extends the spec's own
30+
* `Dashboard` input type (`Omit< Dashboard, … >` over the exclusion list its
31+
* mirror reads), so it inherits the spec's tombstone as a DECLARED member typed
32+
* `undefined` — `aria` is back in the declared key set, but only as a refusal,
33+
* and an authored value is a compile error: the same verdict the Zod twin gives
34+
* at parse. The pins below assert exactly that, with the former neighbours as
35+
* the control. Real enforcement because `packages/types/tsconfig.test.json` is
36+
* chained from this package's `type-check` script (#3009).
3537
*/
3638

3739
import { describe, it, expect } from 'vitest';
@@ -43,18 +45,24 @@ import { DashboardComponentSchema as DashboardComponentZodSchema } from '../zod/
4345
type DeclaredKeys<T> = { [K in keyof T as string extends K ? never : K]: T[K] };
4446
type Declared = keyof DeclaredKeys<DashboardComponentSchema>;
4547

46-
describe('the TS interface no longer declares `aria` (objectui#5830)', () => {
47-
it('`aria` is not a declared member; the neighbours it stood beside still are', () => {
48-
// Type-level pin, erased at runtime: if the member came back, the first
49-
// annotation would collapse to `false` and this file would fail
50-
// `type-check`. (Reverse-verified at the PR: with the member restored,
51-
// `tsc -p tsconfig.test.json` goes red on exactly this line.)
52-
const ariaNotDeclared: 'aria' extends Declared ? false : true = true;
48+
describe('the TS interface declares `aria` only as the spec tombstone (objectui#5830, objectui#9736)', () => {
49+
it('`aria` is declared, typed `undefined`; the neighbours it stood beside still are', () => {
50+
// Type-level pins, erased at runtime. `aria` is a DECLARED key again — the
51+
// spec projection carries the tombstone — and its type admits no value.
52+
const ariaDeclared: 'aria' extends Declared ? true : false = true;
53+
const ariaAdmitsNoValue: [DashboardComponentSchema['aria']] extends [undefined] ? true : false = true;
5354
// Positive controls through the same extraction: a probe that saw no
5455
// members at all would also report `aria` absent.
5556
const widgetsDeclared: 'widgets' extends Declared ? true : false = true;
5657
const dateRangeDeclared: 'dateRange' extends Declared ? true : false = true;
57-
expect(ariaNotDeclared && widgetsDeclared && dateRangeDeclared).toBe(true);
58+
expect(ariaDeclared && ariaAdmitsNoValue && widgetsDeclared && dateRangeDeclared).toBe(true);
59+
});
60+
61+
it('an authored `aria` value is a compile error — no longer absorbed by the index signature', () => {
62+
const legal: DashboardComponentSchema = { type: 'dashboard', widgets: [] };
63+
// @ts-expect-error — `aria` is the spec's retirement tombstone (objectui#9736).
64+
const authored: DashboardComponentSchema = { type: 'dashboard', widgets: [], aria: { ariaLabel: 'Ops' } };
65+
expect([legal.type, authored.type]).toEqual(['dashboard', 'dashboard']);
5866
});
5967
});
6068

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/**
2+
* ObjectUI
3+
* Copyright (c) 2024-present ObjectStack Inc.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
/**
10+
* objectui#9736 — the three hand-written TypeScript twins take the spec BY
11+
* REFERENCE, the way their zod mirrors do (ruling batch #167 item 4, letter 甲).
12+
*
13+
* `AppComponentSchema` / `DashboardComponentSchema` / `PageNodeSchema` each
14+
* extend `Omit< App | Dashboard | Page, … >` over the SAME `as const` exclusion
15+
* array their mirror's `specFieldsExcept` call reads (`APP_SPEC_EXCLUDED`,
16+
* `DASHBOARD_SPEC_EXCLUDED`, `PAGE_SPEC_EXCLUDED`), plus — on the TypeScript
17+
* face only — the twin members whose hand-written type is not assignable to
18+
* the spec's (`header` on the dashboard, `slots` on the page, both already
19+
* ledgered as drift in `zod-mirror-parity.test.ts`), plus the page's
20+
* `assignedProfiles`, which objectstack `main` retires (a forward-compat
21+
* omission, objectui#9409).
22+
*
23+
* ## Why the positive pins are TYPE equalities, not assignments
24+
*
25+
* `BaseSchema` carries `[key: string]: any`, so an object literal carrying ANY
26+
* key compiles against these interfaces whether or not the key is declared —
27+
* an "it compiles" pin would be green on the tree before this change. So:
28+
* - declared-ness is read through `DeclaredKeys`, which filters the index
29+
* signature out (`string extends K`);
30+
* - member types are compared with `Equal`, against the spec's own member;
31+
* - the tombstone half is a `@ts-expect-error` on an authored value, which
32+
* only sticks BECAUSE the key is now declared (the index signature would
33+
* have absorbed it as `any` — `dashboard-aria-retired-contract-twins` is the
34+
* record of that pin not sticking before).
35+
*
36+
* Real enforcement because `packages/types/tsconfig.test.json` is chained from
37+
* this package's `type-check` script; the runtime half asserts the mirror
38+
* gives the same verdict on the same literal.
39+
*/
40+
41+
import { describe, it, expect } from 'vitest';
42+
import type { App, Dashboard, Page } from '@objectstack/spec/ui';
43+
import type { AppComponentSchema } from '../app';
44+
import type { DashboardComponentSchema } from '../complex';
45+
import type { PageNodeSchema } from '../layout';
46+
import {
47+
AppComponentSchema as AppMirror,
48+
DashboardComponentSchema as DashboardMirror,
49+
PageNodeSchema as PageMirror,
50+
} from '../zod/index.zod';
51+
52+
type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2) ? true : false;
53+
type DeclaredKeys<T> = keyof { [K in keyof T as string extends K ? never : K]: T[K] };
54+
55+
/** Keys the spec's document type declares that the twin does NOT declare. */
56+
type SpecKeysUndeclaredOn<Spec, Twin> = Exclude<keyof Spec, DeclaredKeys<Twin>>;
57+
58+
describe('each twin declares every key its spec document type declares', () => {
59+
it('App / Dashboard / Page: the spec-minus-declared key set is empty', () => {
60+
// Before objectui#9736 these read as the MirroredUndeclared rows (20 / 11 / 10
61+
// keys). ⭐ Control: `DeclaredKeys` must see real members, or `never` above is
62+
// vacuous — `protection` / `requires` are keys ONLY the spec projection brings.
63+
const app: Equal<SpecKeysUndeclaredOn<App, AppComponentSchema>, never> = true;
64+
const dashboard: Equal<SpecKeysUndeclaredOn<Dashboard, DashboardComponentSchema>, never> = true;
65+
const page: Equal<SpecKeysUndeclaredOn<Page, PageNodeSchema>, never> = true;
66+
const controlApp: 'protection' extends DeclaredKeys<AppComponentSchema> ? true : false = true;
67+
const controlPage: 'requires' extends DeclaredKeys<PageNodeSchema> ? true : false = true;
68+
const controlNegative: 'notASpecKey9736' extends DeclaredKeys<AppComponentSchema> ? true : false = false;
69+
expect([app, dashboard, page, controlApp, controlPage, controlNegative]).toEqual([true, true, true, true, true, false]);
70+
});
71+
});
72+
73+
describe('admitted spec keys land on the twin with the SPEC\'S type, not `any`', () => {
74+
it('the package-lock envelope and document keys equal the spec member', () => {
75+
const lock: Equal<AppComponentSchema['_lock'], App['_lock']> = true;
76+
const appProtection: Equal<AppComponentSchema['protection'], App['protection']> = true;
77+
const defaultAgent: Equal<AppComponentSchema['defaultAgent'], App['defaultAgent']> = true;
78+
const dashProtection: Equal<DashboardComponentSchema['protection'], Dashboard['protection']> = true;
79+
const dashPackageId: Equal<DashboardComponentSchema['_packageId'], Dashboard['_packageId']> = true;
80+
const pageSource: Equal<PageNodeSchema['source'], Page['source']> = true;
81+
const pageRequires: Equal<PageNodeSchema['requires'], Page['requires']> = true;
82+
const pageInterfaceConfig: Equal<PageNodeSchema['interfaceConfig'], Page['interfaceConfig']> = true;
83+
// Control: an undeclared key reads as `any` through the index signature, and
84+
// `Equal` tells `any` apart from a real member type.
85+
const anyControl: Equal<AppComponentSchema['notASpecKey9736'], App['_lock']> = false;
86+
expect([lock, appProtection, defaultAgent, dashProtection, dashPackageId, pageSource, pageRequires, pageInterfaceConfig, anyControl])
87+
.toEqual([true, true, true, true, true, true, true, true, false]);
88+
});
89+
90+
it('`contextSelectors` — withheld from the projection — is declared over the mirror\'s own element', () => {
91+
const declared: 'contextSelectors' extends DeclaredKeys<AppComponentSchema> ? true : false = true;
92+
const selectors: AppComponentSchema['contextSelectors'] = [
93+
{ id: 'pkg', label: 'Package', optionsSource: { endpoint: '/api/v1/packages' } },
94+
];
95+
expect(declared).toBe(true);
96+
expect(AppMirror.safeParse({ type: 'app', contextSelectors: selectors }).success).toBe(true);
97+
});
98+
});
99+
100+
describe('spec tombstones surface on the twin as a refusal — the verdict the mirror gives', () => {
101+
it('App `version` / Dashboard `refreshInterval`: authoring a value is a compile error AND a parse failure', () => {
102+
// @ts-expect-error — `version` is the spec's `retiredKey()` tombstone.
103+
const app: AppComponentSchema = { type: 'app', version: '1.0.0' };
104+
// @ts-expect-error — `refreshInterval` is the #15680 rename's tombstone.
105+
const dashboard: DashboardComponentSchema = { type: 'dashboard', widgets: [], refreshInterval: 30 };
106+
expect(AppMirror.safeParse(app).success).toBe(false);
107+
expect(DashboardMirror.safeParse(dashboard).success).toBe(false);
108+
});
109+
110+
it('the controls: the same documents without the tombstone key pass both faces', () => {
111+
const app: AppComponentSchema = { type: 'app' };
112+
const dashboard: DashboardComponentSchema = { type: 'dashboard', widgets: [], refreshIntervalSeconds: 30 };
113+
expect(AppMirror.safeParse(app).success).toBe(true);
114+
expect(DashboardMirror.safeParse(dashboard).success).toBe(true);
115+
});
116+
117+
it('`Page` carries no tombstone on the installed pin — its admitted keys pass both faces', () => {
118+
// ⚠️ Recorded rather than assumed: the spec's `PageSchema` has no
119+
// `retiredKey()` member on @objectstack/spec 17.4.0, so the page twin's pin
120+
// is the admitted half only.
121+
const page: PageNodeSchema = { type: 'page', source: 'pages/home.tsx', requires: ['crm'] };
122+
expect(PageMirror.safeParse(page).success).toBe(true);
123+
});
124+
});
125+
126+
describe('the twin-only omissions keep the twin\'s own member, unwidened', () => {
127+
it('Dashboard `header` and Page `slots` are the hand-written types, not the spec\'s', () => {
128+
type HeaderAction = NonNullable<NonNullable<DashboardComponentSchema['header']>['actions']>[number];
129+
const headerLabel: Equal<HeaderAction['label'], string> = true;
130+
const headerNotSpec: Equal<DashboardComponentSchema['header'], Dashboard['header']> = false;
131+
const slotsNotSpec: Equal<PageNodeSchema['slots'], Page['slots']> = false;
132+
expect([headerLabel, headerNotSpec, slotsNotSpec]).toEqual([true, false, false]);
133+
});
134+
135+
it('Page `assignedProfiles` keeps the hand-written `string[]` whatever the spec pin declares', () => {
136+
// A FORWARD-COMPAT omission: objectstack `main` retires the key (its input
137+
// type becomes `undefined`), and the hand-written member would then stop
138+
// compiling in the `extends` clause. Spelling it beside the shared list keeps
139+
// the twin compiling against both the installed pin and `main` (the
140+
// `Spec Main Shape Gate`). Retiring it is objectui#9409's decision, ⛔ not
141+
// this file's, so the member must stay exactly what it was.
142+
const assignedProfiles: Equal<PageNodeSchema['assignedProfiles'], string[] | undefined> = true;
143+
const declared: 'assignedProfiles' extends DeclaredKeys<PageNodeSchema> ? true : false = true;
144+
expect([assignedProfiles, declared]).toEqual([true, true]);
145+
});
146+
});

0 commit comments

Comments
 (0)