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
14 changes: 14 additions & 0 deletions .changeset/9729-mirror-only-published-keys-measured.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
---

Measure the two LOCAL mirrored-but-undeclared keys the `MirroredUndeclared`
ledger carries — `views.zod.ts#DetailViewFieldSchema`'s `dueLike` and
`objectql.zod.ts#ObjectGridSchema`'s `operators` — per key, for objectui#9729's
at-tier contract review. Test only; no package is released by this change, and
neither published accept set moves: the remedy for each key (declare it on the
twin, or narrow the mirror) is the decision this measurement exists to inform.

Also corrects the ledger entry for `dueLike`, which justified itself with a
package-wide name grep. `field-types.ts` does declare that name — on
`DateFieldMetadata` and `DateTimeFieldMetadata`, not on this pair's twin. The
measurement was right and its stated reason was not: a name is not a key.
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* 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.
*/

/**
* An authored `dueLike` on a DETAIL-VIEW FIELD reaches the cell (objectui#9729).
*
* ## Why this reading exists
*
* `views.zod.ts#DetailViewFieldSchema` states `dueLike` and the `DetailViewField`
* twin does not — one of the two LOCAL entries in the `MirroredUndeclared`
* ledger. The contract review that decides which published face moves needs one
* fact the ledger cannot carry: whether anything READS the key on THIS pair's
* authored face, because a key nothing reads is a different decision from one a
* renderer consumes.
*
* ⛔ A source grep cannot answer that. `SchemaRenderer` hands a node's leftover
* keys to the component as props, and this path adds a second hop of its own:
* `DetailSection` spreads the authored field into the enriched bag
* (`enrichDetailField` opens with a spread of the view field) and hands that bag
* to the resolved cell renderer. So the question is asked here the only way it
* can be answered — by rendering, and by watching the drawn text change.
*
* ## The reading, and its control
*
* `end_date` deliberately does NOT match the due/deadline field-NAME convention
* that `resolveDueLike` falls back to, so the authored key is the only thing
* that can turn the affordance on. The control leg renders BY VALUE — it asserts
* the neutral relative wording is present, not merely that "Overdue" is absent,
* which would also be true of a document that drew nothing.
*
* ⛔ This file takes no position on the remedy. It measures a read; whether the
* key should be declared on the twin or removed from the mirror is objectui#9729.
*/

import { describe, it, expect, beforeAll, afterEach } from 'vitest';
import { render, cleanup } from '@testing-library/react';
import * as React from 'react';
import { DetailSection } from '../DetailSection';
import type { DetailViewSection } from '@object-ui/types';

beforeAll(() => {
Object.defineProperty(window, 'innerWidth', { configurable: true, value: 1280 });
});
afterEach(cleanup);

const daysAgo = (n: number): string => {
const d = new Date();
d.setDate(d.getDate() - n);
return d.toISOString();
};

const sectionOf = (fields: unknown[]): DetailViewSection =>
({ title: 'S', fields }) as unknown as DetailViewSection;

/** A past date on a field whose NAME carries no due/deadline convention. */
const BASE = { name: 'end_date', label: 'End', type: 'date', format: 'relative' };
const DATA = { end_date: daysAgo(3) };

describe('an authored `dueLike` on a DetailViewField', () => {
it('reaches the date cell and changes the drawn wording', () => {
render(<DetailSection section={sectionOf([{ ...BASE, dueLike: true }])} data={DATA} />);
expect(document.body.innerHTML).toContain('Overdue');
});

it('CONTROL — the same field without the key draws the neutral wording', () => {
render(<DetailSection section={sectionOf([{ ...BASE }])} data={DATA} />);
const html = document.body.innerHTML;
// Rendered BY VALUE first: an absent affordance means nothing on a document
// that drew nothing at all.
expect(html).toContain('days ago');
expect(html).not.toContain('Overdue');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/**
* 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.
*/

/**
* An authored `operators` on an `object-grid` changes nothing drawn (objectui#9729).
*
* ## Why this reading exists
*
* `objectql.zod.ts#ObjectGridSchema` states `operators` and the `ObjectGridSchema`
* twin does not — the second LOCAL entry in the `MirroredUndeclared` ledger, and
* the one whose mirror line carries its own provenance: a comment saying the key
* was missing from an earlier TypeScript scan. The contract review needs to know
* whether a renderer consumes it.
*
* ⛔ A source grep cannot answer that, because this renderer receives a node's
* leftover keys as props: `ObjectGridRenderer` spreads everything it does not
* itself consume onto `ObjectGrid`. So the question is asked with a RULER — the
* same document drawn twice, compared as bytes — rather than by reading code.
*
* ## The ruler, and why the control comes first
*
* A byte comparison that reports "no difference" is worthless unless the
* instrument can report one. The first case pins that two identical documents
* draw identical bytes (the ruler is stable — no timestamps, no random ids), the
* second is a LIT CONTROL on a key the renderer demonstrably DOES read, and only
* then is the measurement taken. It is taken twice: once on a plain grid and
* once with the filter surface on, since `operators` is a filtering word and a
* grid without a filter affordance would be the wrong corpus to ask.
*
* ⚠️ The claim is bounded by the ruler: this is what the RENDERED output does
* with the key, on these documents. ⛔ It is not a claim that no code anywhere
* could ever read it.
*
* ⛔ This file takes no position on the remedy — objectui#9729 is the card.
*/

import { describe, it, expect, afterEach } from 'vitest';
import { render, screen, cleanup } from '@testing-library/react';
import React from 'react';
import { ObjectGridRenderer } from '../index';

afterEach(cleanup);

const BASE: Record<string, unknown> = {
type: 'object-grid',
objectName: 'probe',
columns: ['name'],
staticData: [
{ id: '1', name: 'Alpha' },
{ id: '2', name: 'Beta' },
],
};

/** Draw one document and return its markup, once the rows are on screen. */
async function draw(schema: Record<string, unknown>): Promise<string> {
const { container } = render(<ObjectGridRenderer schema={schema} />);
await screen.findByText('Alpha', {}, { timeout: 5000 });
return container.innerHTML;
}

describe('the ruler', () => {
it('two identical documents draw identical bytes', async () => {
const a = await draw({ ...BASE });
cleanup();
const b = await draw({ ...BASE });
expect(b).toBe(a);
});

it('LIT CONTROL — a key the renderer DOES read moves the bytes', async () => {
const a = await draw({ ...BASE });
cleanup();
const b = await draw({ ...BASE, label: 'GRID-CAPTION-9729' });
expect(b).not.toBe(a);
expect(b).toContain('GRID-CAPTION-9729');
});
});

describe('an authored `operators`', () => {
it('changes nothing the grid draws', async () => {
const a = await draw({ ...BASE });
cleanup();
const b = await draw({ ...BASE, operators: { name: ['equals', 'contains'] } });
expect(b).toBe(a);
});

it('changes nothing with the filter surface on either', async () => {
const FILTERS = { ...BASE, showFilters: true, searchableFields: ['name'] };
const a = await draw({ ...FILTERS });
cleanup();
const b = await draw({ ...FILTERS, operators: { name: ['equals', 'contains'] } });
expect(b).toBe(a);
});
});
182 changes: 182 additions & 0 deletions packages/types/src/__tests__/mirror-only-published-keys-9729.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/**
* 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.
*/

/**
* The two LOCAL mirrored-but-undeclared keys, measured per key (objectui#9729).
*
* ## What this file is, and ⛔ what it is not
*
* `MirroredUndeclared` in `zod-mirror-parity.test.ts` measures the DIRECTION —
* a key the zod mirror states and the TypeScript twin does not — and reconciles
* it per pair. It deliberately says nothing about what each key DOES, because
* the remedy is a contract decision: declaring the key on the twin ENLARGES the
* published TypeScript accept set, narrowing the mirror SHRINKS the published
* validator's, and both move a face `@object-ui/types` already ships.
*
* ⛔ This file does not pick either direction, and it is ⛔ not a repair. It is
* the per-key CONSEQUENCE the ledger's one-line entries cannot carry, recorded
* so the at-tier contract review can read it instead of re-deriving it. Every
* assertion below is a statement about `origin/main` as it stands; whichever
* direction the review rules, the entries here move with it.
*
* ## The two keys do NOT behave the same way, and the difference is the ruling
*
* Both twins are hand-written interfaces, but only one of them can REFUSE:
*
* - `DetailViewField` carries no index signature, so TypeScript rejects an
* authored `dueLike` outright (`TS2353`). The two published faces therefore
* actively CONTRADICT each other: the validator judges the key and keeps
* it, the compiler refuses the same document.
* - `ObjectGridSchema` extends `BaseSchema`, whose `[key: string]: any` index
* signature absorbs any unstated key. TypeScript neither declares nor
* refuses `operators`; it types it `any`. The faces do not contradict — one
* is simply silent, which is a weaker defect and a different decision.
*
* ## ⚠️ The third key the ledger groups with these two is NOT this defect
*
* `complex.zod.ts#DashboardConfigSchema`'s `aria` is a `z.never()` retirement
* tombstone: it REFUSES the key by name and prints a remedy. Its consequence is
* the OPPOSITE of the two above — they admit silently, it rejects loudly — so a
* sweep that treats the three alike would ask a maintainer to repair something
* already doing its job. Pinned here as the CONTRAST, by re-deriving its shape
* rather than by citing the reading.
*
* ## Every zero below carries a lit control on the same instrument
*
* A green `safeParse` proves nothing on its own: both mirrors accept unknown
* keys (one strips them, the other keeps them unexamined). What distinguishes a
* MIRRORED key from an unknown one is that the mirror JUDGES it — so each key's
* pin is paired with an unrecognised-key control parsed through the same schema.
*/

import { describe, it, expect } from 'vitest';
import { DetailViewFieldSchema } from '../zod/views.zod.js';
import { ObjectGridSchema } from '../zod/objectql.zod.js';
import { DashboardConfigSchema } from '../zod/complex.zod.js';
import type { DetailViewField } from '../views.js';
import type { ObjectGridSchema as ObjectGridSchemaType } from '../objectql.js';

/** A control key no surface in this package declares. */
const UNKNOWN_KEY = 'zzzNotAKeyAnySurfaceDeclares9729';

describe('views.zod.ts#DetailViewFieldSchema — `dueLike`', () => {
it('the mirror JUDGES the key, while an unknown key is stripped unexamined', () => {
const judged = DetailViewFieldSchema.safeParse({ name: 'end_date', dueLike: true });
expect(judged.success).toBe(true);
// KEPT, not stripped — the mirror states it.
expect(judged.success && judged.data).toMatchObject({ dueLike: true });

// LIT CONTROL, same schema, same document shape: an unstated key survives
// the parse and is GONE from the output. So "green" is not the evidence —
// the key surviving into `data` is.
const control = DetailViewFieldSchema.safeParse({ name: 'end_date', [UNKNOWN_KEY]: true });
expect(control.success).toBe(true);
expect(control.success && Object.keys(control.data)).not.toContain(UNKNOWN_KEY);
});

it('the mirror refuses a wrong-typed `dueLike` BY NAME', () => {
const r = DetailViewFieldSchema.safeParse({ name: 'end_date', dueLike: 'yes' });
expect(r.success).toBe(false);
expect(!r.success && r.error.issues.map((i) => i.path.join('.'))).toContain('dueLike');
});
});

describe('objectql.zod.ts#ObjectGridSchema — `operators`', () => {
it('the mirror JUDGES the key, while an unknown key is kept unexamined', () => {
const judged = ObjectGridSchema.safeParse({
type: 'object-grid',
objectName: 'probe',
operators: { name: ['equals'] },
});
expect(judged.success).toBe(true);

// LIT CONTROL: this mirror's base passes unknown keys THROUGH, so an
// unstated key is kept as-is and is never type-checked. The distinguishing
// evidence for a mirrored key is therefore the refusal below, not the pass.
const control = ObjectGridSchema.safeParse({
type: 'object-grid',
objectName: 'probe',
[UNKNOWN_KEY]: 42,
});
expect(control.success).toBe(true);
});

it('the mirror refuses a wrong-typed `operators` BY NAME, and ignores the control', () => {
const judged = ObjectGridSchema.safeParse({
type: 'object-grid',
objectName: 'probe',
operators: 42,
});
expect(judged.success).toBe(false);
expect(!judged.success && judged.error.issues.map((i) => i.path.join('.'))).toContain('operators');

const control = ObjectGridSchema.safeParse({
type: 'object-grid',
objectName: 'probe',
[UNKNOWN_KEY]: 42,
});
expect(control.success).toBe(true);
});
});

describe('complex.zod.ts#DashboardConfigSchema — `aria` is the CONTRAST, not the defect', () => {
it('REFUSES the key by name and names a remedy — the opposite consequence', () => {
const r = DashboardConfigSchema.safeParse({ aria: { label: 'x' } });
expect(r.success).toBe(false);
const issue = !r.success ? r.error.issues.find((i) => i.path.join('.') === 'aria') : undefined;
expect(issue).toBeDefined();
// The remedy travels with the refusal. Asserted as the leading token rather
// than the whole sentence: the wording is the contract here, the prose that
// follows it is not.
expect(String(issue?.message)).toContain('RETIRED');

// LIT CONTROL on the same schema: an unrecognised key is NOT refused, so
// the refusal above is BY NAME and not a strictness the whole object has.
const control = DashboardConfigSchema.safeParse({ [UNKNOWN_KEY]: true });
expect(control.success).toBe(true);
});

it('a document that omits the key stays green — a tombstone gates nothing else', () => {
expect(DashboardConfigSchema.safeParse({ showHeader: true }).success).toBe(true);
});
});

/* ── The TypeScript side, where the two keys stop behaving alike ───────────── */

/**
* `DetailViewField` REFUSES `dueLike`. The `@ts-expect-error` below IS the
* assertion: it fails the package's `tsc -p tsconfig.test.json` leg in both
* directions — if the key stops being refused (someone declares it on the twin,
* which is one of the two remedies under review) the directive becomes unused
* and TypeScript reports it.
*/
// @ts-expect-error objectui#9729 — `dueLike` is not a member of `DetailViewField`.
export const authoredDueLike: DetailViewField = { name: 'end_date', dueLike: true };

/** LIT CONTROL: a key the twin DOES declare is accepted on the same literal. */
export const authoredCurrency: DetailViewField = { name: 'end_date', currency: 'USD' };

/**
* `ObjectGridSchema` does NOT refuse `operators` — and the absence of a
* `@ts-expect-error` here is the measurement, not an omission: adding one
* reddens the same type-check leg as unused, which is how this claim fails if
* the twin ever loses `BaseSchema`'s index signature.
*/
export const authoredOperators: ObjectGridSchemaType = {
type: 'object-grid',
objectName: 'probe',
operators: { name: ['equals'] },
};

describe('the TypeScript twins', () => {
it('are compiled by this package’s type-check leg, which is where the two directives above are read', () => {
// The runtime here only keeps the three bindings alive; the assertions that
// matter are the directives, and `tsc -p tsconfig.test.json` is the reader.
expect([authoredDueLike, authoredCurrency, authoredOperators]).toHaveLength(3);
});
});
Loading
Loading