Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .changeset/dashboard-aria-member-retired-5830.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
'@object-ui/types': minor
---

**Published TS surface narrowed:** `DashboardComponentSchema` no longer declares
the `aria` member (`{ ariaLabel?, ariaDescribedBy?, role? }`). Its doc comment
claimed alignment with `@objectstack/spec AriaPropsSchema`, but the spec removed
`dashboard.aria` at the #3896 audit close-out — `DashboardSchema.shape.aria` is
a tombstone that refuses any value and tells authors to delete the key — and no
dashboard renderer ever read `schema.aria` (objectui#5830).

What an author loses is the **type-level suggestion** only: the key was already
refused at parse (the Zod twin inherits the spec tombstone by reference), and
`BaseSchema`'s index signature means an existing `aria:` line still compiles.
There is **no runtime behaviour change** — the key never rendered, and stored
documents carrying it already failed validation before this release.
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* ObjectUI
* Copyright (c) 2024-present ObjectStack Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

/**
* Retirement pin — `DashboardComponentSchema.aria` (objectui#5830).
*
* The spec removed `dashboard.aria` at the #3896 audit close-out ("no
* dashboard renderer ever applied it"): `DashboardSchema.shape.aria` in
* `@objectstack/spec/ui` is a tombstone that refuses any value and tells
* authors to delete the key. objectui's Zod twin inherits that refusal by
* reference (`SpecDashboardFields`, `zod/complex.zod.ts` — `aria` is not in
* its exclusion list), and `packages/plugin-dashboard/src` has no
* `schema.aria` read site (pinned by that package's
* `dashboardAuthoredInputs.test.tsx`). The TS interface was the last surface
* still DECLARING the key — under a comment claiming alignment with
* `AriaPropsSchema`, the opposite of the contract: the member-level instance
* of #4631's "declared surfaces disagree".
*
* What the deletion changes at the type level, stated honestly: `BaseSchema`
* carries `[key: string]: any`, so an authored `aria:` on a dashboard literal
* still COMPILES after the removal — it falls to the index signature. A
* `@ts-expect-error` pin on an authored literal therefore cannot stick here
* (unlike `default-children-retired-contract-twins.test.ts`, whose interface
* has no index signature). The pinnable effect is that `aria` stops being a
* DECLARED member: the probe below extracts the interface's literal key set —
* the index signature is filtered out by `string extends K` — and asserts
* `aria` is out while its former neighbours stay in. Real enforcement because
* `packages/types/tsconfig.test.json` is chained from this package's
* `type-check` script (#3009).
*/

import { describe, it, expect } from 'vitest';
import type { DashboardComponentSchema } from '../complex';
import { DashboardComponentSchema as DashboardComponentZodSchema } from '../zod/index.zod';

// Literal (declared) keys of T: `string extends K` is true only for the index
// signature's key, so mapping it to `never` leaves exactly the authored members.
type DeclaredKeys<T> = { [K in keyof T as string extends K ? never : K]: T[K] };
type Declared = keyof DeclaredKeys<DashboardComponentSchema>;

describe('the TS interface no longer declares `aria` (objectui#5830)', () => {
it('`aria` is not a declared member; the neighbours it stood beside still are', () => {
// Type-level pin, erased at runtime: if the member came back, the first
// annotation would collapse to `false` and this file would fail
// `type-check`. (Reverse-verified at the PR: with the member restored,
// `tsc -p tsconfig.test.json` goes red on exactly this line.)
const ariaNotDeclared: 'aria' extends Declared ? false : true = true;
// Positive controls through the same extraction: a probe that saw no
// members at all would also report `aria` absent.
const widgetsDeclared: 'widgets' extends Declared ? true : false = true;
const dateRangeDeclared: 'dateRange' extends Declared ? true : false = true;
expect(ariaNotDeclared && widgetsDeclared && dateRangeDeclared).toBe(true);
});
});

describe('the Zod twin refuses the key by name — the spec tombstone, inherited by reference', () => {
const legal = { type: 'dashboard' as const, widgets: [] };

it('a legal dashboard parses green — the control for the refusal below', () => {
expect(DashboardComponentZodSchema.safeParse(legal).success).toBe(true);
});

it('an arbitrary unknown key does not refuse — the red below is the tombstone, not strictness', () => {
// BaseSchema is not `.strict()`: an undeclared key rides through (or is
// stripped) but never refuses. So a red `aria` can only come from the
// DECLARED tombstone flowing in from the spec — the thing being pinned.
const r = DashboardComponentZodSchema.safeParse({ ...legal, objectui5830NotAKey: 'x' });
expect(r.success).toBe(true);
});

it('`aria` is refused at its own path, with the removal message', () => {
const r = DashboardComponentZodSchema.safeParse({ ...legal, aria: { ariaLabel: 'Ops' } });
expect(r.success).toBe(false);
if (r.success) return;
const issue = r.error.issues.find((i) => i.path.join('.') === 'aria');
expect(issue, 'no issue at path `aria`').toBeTruthy();
expect(issue!.message).toMatch(/removed/);
});
});
21 changes: 10 additions & 11 deletions packages/types/src/__tests__/p1-spec-alignment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -606,17 +606,16 @@ describe('P1.6 i18n & ARIA Protocol Alignment', () => {
expect(schema.aria?.live).toBe('polite');
});

it('should accept ARIA props on DashboardComponentSchema', () => {
const schema: DashboardComponentSchema = {
type: 'dashboard',
widgets: [],
aria: {
ariaLabel: 'Sales Dashboard',
role: 'region',
},
};
expect(schema.aria?.ariaLabel).toBe('Sales Dashboard');
});
// `should accept ARIA props on DashboardComponentSchema` REMOVED
// (objectui#5830): the spec removed `dashboard.aria` at the #3896 audit
// close-out — `DashboardSchema.shape.aria` is a tombstone that refuses any
// value — and `packages/plugin-dashboard/src` has no `schema.aria` read site
// (measured for #5742; pinned by that package's
// `dashboardAuthoredInputs.test.tsx`). The declared member is gone from the
// TS interface too; had this test stayed, it would have kept passing through
// `BaseSchema`'s index signature while asserting the opposite of the
// contract. The removal is pinned by
// `dashboard-aria-retired-contract-twins.test.ts`.

it('should accept ARIA props on PageNodeSchema', () => {
const schema: PageNodeSchema = {
Expand Down
20 changes: 11 additions & 9 deletions packages/types/src/complex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -844,15 +844,17 @@ export interface DashboardComponentSchema extends BaseSchema {
defaultRange?: SpecDateRangeDefaultRange;
allowCustomRange?: boolean;
};
/**
* ARIA accessibility attributes.
* Aligned with @objectstack/spec AriaPropsSchema.
*/
aria?: {
ariaLabel?: string;
ariaDescribedBy?: string;
role?: string;
};
// `aria` was DECLARED here until objectui#5830, under a comment claiming
// alignment with @objectstack/spec AriaPropsSchema — by then the opposite of
// the contract: the spec removed `dashboard.aria` at the #3896 audit
// close-out (no dashboard renderer ever applied it), so
// `DashboardSchema.shape.aria` is a tombstone that refuses any value, the
// Zod twin (`zod/complex.zod.ts`) inherits that refusal through
// `SpecDashboardFields`, and `plugin-dashboard` has no `schema.aria` read
// site. Note `BaseSchema`'s index signature still types an authored `aria`
// as `any` — this deletion removes the type-level suggestion and the false
// parity claim, not a key that ever rendered. Pinned by
// `__tests__/dashboard-aria-retired-contract-twins.test.ts`.
}

/**
Expand Down
Loading