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
42 changes: 42 additions & 0 deletions .changeset/dashboard-config-aria-retired-5852.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
---
'@object-ui/types': minor
---

**Retired the designer-surface dashboard `aria` pair — `DashboardConfig.aria` and `DashboardConfigSchema.aria`** (objectui#5852).

Both spellings are named verbatim above so a host can grep its own sources: the
retired member is `aria`, on the TypeScript interface `DashboardConfig`
(`@object-ui/types`, `designer.ts`) and on its Zod mirror `DashboardConfigSchema`
(`@object-ui/types/zod`). It declared `{ label?: string; description?: string }`.

**Why.** The spellings `label`/`description` match neither `@objectstack/spec`'s
`AriaProps` vocabulary (`ariaLabel` / `ariaDescribedBy` / `role`) nor anything a
renderer maps, so no read point could have consumed them even in principle.
Re-measured on `main` at the retirement: zero `.aria` reads in
`packages/plugin-designer/src`, `packages/plugin-dashboard/src` and
`apps/console/src`; zero occurrences of either name anywhere in the `objectstack`
repo; and `DashboardConfigPanel.tsx` — the panel the interface's own doc comment
says it serves — imports neither name.

**The two directions differ, and neither is a no-op:**

- **TypeScript (a narrowed suggestion, not a compile break).** `DashboardConfig`
carries a `[key: string]: any` catch-all, so an existing `aria:` line still
compiles; what is gone is the editor suggestion and the false implication that
the key was part of the contract.
- **Zod (a behaviour change — read this one).** `aria` is now an ADR-0049
retirement tombstone (`z.never().optional()`), following this package's
existing convention. Previously an authored `aria` was **accepted and
preserved** in `safeParse` output; it is now **refused by name**, with `aria`
in the issue path and a message telling the author to delete the key. A plain
deletion was deliberately not taken: `DashboardConfigSchema` is a bare
`z.object` with no `.strict()`, so deleting the key would have made an
authored `aria` **silently disappear** from the parsed output instead — a
quiet data loss in place of a loud refusal.

**External caveat.** In-repo consumer count is zero, but that is not the npm
count: `@object-ui/types` is published, and stored dashboard configuration is
not reachable from this repo. A host that authored `aria` on a `DashboardConfig`
document will now see a validation error naming the key where it previously saw
a silently carried value. The remedy is to delete the key — it never reached a
renderer.
42 changes: 40 additions & 2 deletions packages/types/src/__tests__/dashboard-config.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -62,7 +62,11 @@ describe('DashboardConfig TypeScript Types', () => {
headerActions: [
{ label: 'Export', action: 'export', icon: 'Download', variant: 'outline' },
],
aria: { label: 'Sales dashboard', description: 'Interactive sales overview' },
// `aria: { label, description }` REMOVED (objectui#5852): the member is
// gone from the `DashboardConfig` declaration. Kept here it would have
// gone on compiling through the interface's `[key: string]: any`
// catch-all while asserting the opposite of the contract — the
// green-wash objectui#5830 called out on the sibling member.
};
expect(config.widgets).toHaveLength(1);
expect(config.widgets![0].type).toBe('metric');
Expand DownExpand Up@@ -199,10 +203,44 @@ describe('DashboardConfig Zod Validation', () => {
expect(result.success).toBe(true);
});

it('should validate aria accessibility attributes', () => {
// `should validate aria accessibility attributes` was FLIPPED, not deleted
// (objectui#5852). It asserted `success === true` for an authored `aria`.
// After the retirement a PLAIN DELETION would have kept it green — this
// schema is a bare `z.object` with no `.strict()`, so an undeclared key is
// accepted and silently stripped (measured; the same behaviour objectui#6068
// recorded). That green would have meant "nothing looked", which is why the
// mirror carries a `z.never()` tombstone instead and this pin now asserts the
// refusal by name.
it('refuses the retired `aria` key by name, with the removal message', () => {
const result = DashboardConfigSchema.safeParse({
aria: { label: 'Sales dashboard', description: 'Interactive overview' },
});
expect(result.success).toBe(false);
if (result.success) return;
const issue = result.error.issues.find((i) => i.path.join('.') === 'aria');
expect(issue, 'no issue at path `aria`').toBeTruthy();
// The message is asserted, not just its existence: a `z.never()` with no
// `error` would refuse with zod's generic "expected never, received
// object", which names the key only via the path and tells the author
// nothing about what to do. The tombstone carries a real message.
expect(issue!.message).toMatch(/RETIRED \(objectui#5852\)/);
expect(issue!.message).toMatch(/delete the key/);
});

it('CONTROL: an arbitrary undeclared key is NOT refused — the red above is the tombstone, not strictness', () => {
// Without this control, the refusal above is equally consistent with the
// schema having become `.strict()`, which would refuse every unknown key.
const result = DashboardConfigSchema.safeParse({ objectui5852NotAKey: 'x' });
expect(result.success).toBe(true);
// ...and it is dropped from the output, which is exactly what a plain
// deletion of `aria` would have silently done to an authored value.
expect(result.success && 'objectui5852NotAKey' in result.data).toBe(false);
});

it('CONTROL: a legal config still parses green — the tombstone narrowed nothing else', () => {
const result = DashboardConfigSchema.safeParse({
title: 'Sales', columns: 12, showHeader: true,
});
expect(result.success).toBe(true);
});
});
29 changes: 23 additions & 6 deletions packages/types/src/designer.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -620,12 +620,29 @@ export interface DashboardConfig {
}>;

// -- Accessibility ---------------------------------------------------------

/** ARIA properties */
aria?: {
label?: string;
description?: string;
};
//
// `aria?: { label?, description? }` was DECLARED here until objectui#5852.
// It was never a contract: the spellings (`label`/`description`) match
// neither `@objectstack/spec`'s `AriaProps` (`ariaLabel` / `ariaDescribedBy`
// / `role`) nor anything a renderer maps, so no read point could have
// consumed it even in principle. Measured on `origin/main` at the retirement:
// zero `.aria` reads in `packages/plugin-designer/src`,
// `packages/plugin-dashboard/src` and `apps/console/src` (the same grep
// family finds the live `schema.aria` reads in `plugin-detail`'s
// `record-quick-actions.tsx` and `plugin-list`'s `ListView.tsx`), and zero
// occurrences of either name in the `objectstack` repo.
//
// `DashboardConfigPanel.tsx` — the panel this interface's own doc comment
// says it serves — imports `ConfigPanelSchema` from `@object-ui/components`
// and neither `DashboardConfig` nor `DashboardConfigSchema`, so the key
// documented an integration that does not exist.
//
// Note the `[key: string]: any` catch-all below still types an authored
// `aria` as `any`: this deletion removes the type-level SUGGESTION, not a
// key that ever rendered (same shape as objectui#5830 on
// `DashboardComponentSchema.aria`). The Zod twin does carry teeth — see the
// `z.never()` tombstone in `zod/complex.zod.ts`. Pinned by
// `__tests__/dashboard-config.test.ts`.

/** Catch-all for additional properties */
[key: string]: any;
Expand Down
17 changes: 13 additions & 4 deletions packages/types/src/zod/complex.zod.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -563,6 +563,18 @@ export const DashboardWidgetConfigSchema = z.object({
* Dashboard Config Schema — Zod validator for DashboardConfigPanel data model.
*
* Validates the unified dashboard configuration used by create/edit workflows.
*
* The `aria` member is an ADR-0049 retirement tombstone (objectui#5852),
* following this package's convention (`data-display.zod.ts`
* `StaticTableColumnSchema`, the set `crud.zod.ts` `confirm` established):
* `z.never().optional()` REFUSES an authored value at parse time with the key
* named in the error path, rather than letting it be silently stripped the way
* an undeclared key would be on this non-`.strict()` object. Loud refusal is
* the ruled outcome — `aria` was accepted-and-preserved for as long as it was
* declared, so a plain deletion would have converted a preserved key into a
* silent drop. The TS twin (`../designer.ts` `DashboardConfig`) no longer
* declares it at all; both halves are pinned by
* `__tests__/dashboard-config.test.ts`.
*/
export const DashboardConfigSchema = z.object({
id: z.string().optional().describe('Dashboard identifier'),
Expand DownExpand Up@@ -592,10 +604,7 @@ export const DashboardConfigSchema = z.object({
icon: z.string().optional(),
variant: z.string().optional(),
})).optional().describe('Header action buttons'),
aria: z.object({
label: z.string().optional(),
description: z.string().optional(),
}).optional().describe('ARIA accessibility attributes'),
aria: z.never({ error: 'RETIRED (objectui#5852) — `aria` is no longer part of DashboardConfig; delete the key. The `{ label, description }` spellings matched no renderer vocabulary and nothing ever read them.' }).optional().describe('RETIRED (objectui#5852) — the `{ label, description }` spellings matched no renderer vocabulary and no read point ever consumed them; delete the key. For real ARIA use the spec vocabulary (`ariaLabel` / `ariaDescribedBy` / `role`) on a surface that reads it.'),
});

/**
Expand Down
Loading