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
36 changes: 36 additions & 0 deletions .changeset/mapdensity-dead-rowheight-spellings-4352.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
'@object-ui/react': minor
---

`bridgeListView` maps the five row heights the spec admits, and only those — the four dead spellings are gone

`mapDensity` carried a nine-key table: `compact`, `short`, `comfortable`,
`spacious`, `small`, `medium`, `large`, `tall`, `extra_tall`. `RowHeightSchema`
in `@objectstack/spec` admits five — `short | compact | medium | tall |
extra_tall` — so `comfortable`, `spacious`, `small` and `large` were unreachable
from any spec-valid list view. The bridge's own parameter type said as much
(`Partial< ListView >`), and `mapDensity` widened it back to `rowHeight?: string`
to let them in. They survived because the fixture asserting three of them
compiled against nothing: the package's build tsconfig excludes tests and no
other `tsc` read them, so four branches of renderer-side dialect read as live
capability (objectui#4352, surfaced by objectui#4040 / PR #4351).

They are deleted. The parameter takes the spec type honestly, and the table is
now `Record< RowHeight, … >`, so a row height added upstream fails the build here
instead of arriving with no density. This is AGENTS.md #0.1: a lenient reading
for off-spec metadata is a second de-facto contract, and one strict contract
beats N dialects — a bad `rowHeight` gets fixed at the producer, where the schema
already rejects it.

**Breaking semantics, deliberately graded `minor`** (this repo never publishes
`major` — its major tracks `@objectstack`). Nothing narrows in the published type
surface: `mapDensity` is module-local and never appeared in the emitted `.d.ts`,
and `bridgeListView`'s declaration is unchanged. What changes is runtime output,
and only for input the spec already rejects: a host handing the bridge
`rowHeight: 'comfortable'` (or `'spacious'` / `'small'` / `'large'`) used to get
`density: 'comfortable'` / `'spacious'` / `'compact'` back, and now gets no
`density` key at all, so the renderer's own default applies. A sweep of this repo,
the `objectstack` example apps and the console's view metadata found zero authored
uses of any of the four; the legacy `densityMode` alias cannot produce one either,
since `DENSITY_MODE_TO_ROW_HEIGHT` is typed `Record< DensityMode, RowHeight >` and
folds onto `compact` / `medium` / `tall`.
40 changes: 40 additions & 0 deletions packages/react/src/spec-bridge/__tests__/SpecBridge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,46 @@ describe('SpecBridge', () => {
expect(extraTall.density).toBe('spacious');
});

// The other half of that enum: `comfortable` / `spacious` / `small` /
// `large` used to be mapped too, so four values `RowHeightSchema` does not
// admit read as live capability. They are gone (objectui#4352) — AGENTS.md
// #0.1, one strict contract beats N dialects — and an off-spec `rowHeight`
// now falls through to no density at all rather than being quietly
// rehabilitated into one.
//
// Routed through `SpecBridge.transformListView`, whose parameter is `any`:
// that untyped boundary is the one a host's stored JSON actually crosses,
// and it is the only way left to get these values into the bridge. Writing
// them on `bridgeListView` directly no longer type-checks, which is the
// static half of the same fix.
it.each(['comfortable', 'spacious', 'small', 'large'])(
'leaves density unset for the off-spec rowHeight %s',
(rowHeight) => {
const node = new SpecBridge().transformListView({
name: 'off_spec_density',
rowHeight,
});

expect(node.density).toBeUndefined();
// Not merely undefined — the key is never written, so the renderer's
// own default applies instead of an explicit `density: undefined`.
expect('density' in node).toBe(false);
},
);

// Control: a string in neither vocabulary already fell through before the
// four keys were deleted, and still does. Green on both sides of the
// change — it pins the fall-through itself, not the deletion.
it('leaves density unset for a rowHeight in no vocabulary at all', () => {
const node = new SpecBridge().transformListView({
name: 'off_spec_density',
rowHeight: 'gargantuan',
});

expect(node.density).toBeUndefined();
expect('density' in node).toBe(false);
});

it('includes optional list properties', () => {
const node = bridgeListView(
{
Expand Down
42 changes: 28 additions & 14 deletions packages/react/src/spec-bridge/bridges/list-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import type { SchemaNode } from '@object-ui/core';
import type { BridgeContext, BridgeFn } from '../types';
import type { ListView, ListColumn } from '@objectstack/spec/ui';
import type { ListView, ListColumn, RowHeight } from '@objectstack/spec/ui';

/**
* Bridge input: the spec-canonical ListView (#2231 — the former hand-written
Expand Down Expand Up @@ -45,22 +45,36 @@ function mapColumn(col: ListColumn | string): Record<string, any> {
return mapped;
}

/**
* The spec's five row heights collapsed onto the renderer's three densities.
*
* `Record<RowHeight, …>` on purpose (objectui#4352): the key set is the spec's,
* so a row height added upstream fails the build here instead of silently
* arriving with no density. The table used to carry four more keys —
* `comfortable`, `spacious`, `small`, `large` — which `RowHeightSchema` does
* not admit, so no spec-valid list view could ever reach them; they only
* survived because the parameter was widened back to `string` and the fixture
* asserting three of them compiled against nothing. Deleting them is AGENTS.md
* #0.1: a renderer-side dialect for off-spec metadata is a second de-facto
* contract, and one strict contract beats N. An off-spec `rowHeight` now falls
* through to `undefined` — the producer is where it gets fixed.
*/
const ROW_HEIGHT_TO_DENSITY: Record<
RowHeight,
'compact' | 'comfortable' | 'spacious'
> = {
compact: 'compact',
short: 'compact',
medium: 'comfortable',
tall: 'spacious',
extra_tall: 'spacious',
};

function mapDensity(
rowHeight?: string,
rowHeight?: RowHeight,
): 'compact' | 'comfortable' | 'spacious' | undefined {
if (!rowHeight) return undefined;
const map: Record<string, 'compact' | 'comfortable' | 'spacious'> = {
compact: 'compact',
short: 'compact',
comfortable: 'comfortable',
spacious: 'spacious',
small: 'compact',
medium: 'comfortable',
large: 'spacious',
tall: 'spacious',
extra_tall: 'spacious',
};
return map[rowHeight];
return ROW_HEIGHT_TO_DENSITY[rowHeight];
}

/** Transforms a ListView spec into a DataTable SchemaNode */
Expand Down
Loading