Skip to content

Commit cbe6d22

Browse files
committed
fix(core): update test files for the batch 4 renames; add changeset
Test files are not covered by `turbo type-check` (78/78 was green while these were still stale), so the renames surfaced only in the test run: PluginSystem.test.ts (PluginDefinition) and managedBy.test.ts (resolveCrudAffordances). Worth recording as a method note — a green type-check does NOT mean the rename is complete. The plugin-charts comments naming `ChartSeries.type` / `.stack` are left untouched: those refer to the SPEC's ChartSeries, not the renamed core type. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Jdef6ZFhJmiNRhNCd4DW3
1 parent caf6324 commit cbe6d22

3 files changed

Lines changed: 88 additions & 34 deletions

File tree

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
---
2+
"@object-ui/core": minor
3+
"@object-ui/app-shell": patch
4+
"@object-ui/plugin-form": patch
5+
"@object-ui/plugin-grid": patch
6+
"@object-ui/plugin-list": patch
7+
---
8+
9+
Stop declaring 13 `@object-ui/core` symbols under names `@objectstack/spec` owns
10+
(objectui#3158, objectstack#4115 batch 4).
11+
12+
**Breaking for importers of `@object-ui/core`** — seven exported names changed,
13+
because the spec exports the same name for a *different* thing:
14+
15+
| was | now | what the spec's same-named export actually is |
16+
|:--|:--|:--|
17+
| `ChartSeries` | `ChartSeriesBinding` | the authored dataset-binding descriptor (a measure `name`, no `data`) |
18+
| `ActionHandler` | `ActionRunnerHandler` | the SERVER-side objectql handler, `(ctx) => unknown` |
19+
| `PluginDefinition` | `RegistryPluginDefinition` | the platform PACKAGE manifest (`id`/`slug`/`staticPath`/install hooks) |
20+
| `ValidationError` | `SchemaNodeValidationError` | plugin-manifest validation, keyed by `field`, no severity |
21+
| `ValidationResult` | `SchemaNodeValidationResult` | ditto, with both arrays optional |
22+
| `defineView` | `defineSystemView` | the VIEW-DOCUMENT factory: parses a `ViewSchema`, returns a validated `View` |
23+
| `resolveCrudAffordances` | `resolveEffectiveCrudAffordances` | the object-level affordance matrix, with no notion of server API operations |
24+
25+
The other six keep their names and are now **imported from the spec** instead of
26+
re-declared: `StyleMap`, `ResponsiveStyles` (ADR-0065), `RowHeight`,
27+
`CONTEXT_TOKENS`, `CrudAffordances`, `RowCrudPredicates`.
28+
29+
**The copies were live misdescriptions, not just duplicates.** Three said so in
30+
their own comments:
31+
32+
- `CONTEXT_TOKENS` carried a note that the duplication was "temporary until the
33+
next coordinated release… because the installed `@objectstack/spec` predates
34+
that export". The installed spec (17.0.0-rc.0) exports it, and the copy was
35+
byte-identical — so it passed every value comparison and every behavioural
36+
test for the whole interval in which its stated reason was false.
37+
- `RowHeight` advertised itself as "the spec's `RowHeightSchema` vocabulary"
38+
while being a hand-written union. It happened to be correct; nothing would
39+
have caught the day it stopped being.
40+
- `managedBy.ts` described itself as a "UI-side mirror of the framework's
41+
`resolveCrudAffordances()`" and carried its own `DEFAULTS` table — a
42+
line-for-line copy of the spec's `CRUD_AFFORDANCE_DEFAULTS`, plus a copy of
43+
its override parser.
44+
45+
`resolveEffectiveCrudAffordances` now **delegates** the bucket/`userActions` half
46+
to the spec's `resolveCrudAffordances()`, so the bucket table has exactly one
47+
definition on the platform. What stays objectui's is the part the spec has no
48+
notion of: intersecting that matrix with the server-resolved effective API
49+
operation set (#3391), so the UI never offers a button the server would 405 —
50+
and the name now says that instead of claiming to be the spec's function.
51+
52+
Deriving `RowCrudPredicates` also **tightens** it: the local copy typed
53+
`visibleWhen`/`disabledWhen` as `unknown`, where the spec types them as
54+
`Expression | ExpressionInput`. That was imprecision, not a deliberate dialect.

packages/core/src/registry/__tests__/PluginSystem.test.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import { describe, it, expect, vi, beforeEach } from 'vitest';
10-
import { PluginSystem, type PluginDefinition } from '../PluginSystem';
10+
import { PluginSystem, type RegistryPluginDefinition } from '../PluginSystem';
1111
import { Registry } from '../Registry';
1212

1313
describe('PluginSystem', () => {
@@ -20,7 +20,7 @@ describe('PluginSystem', () => {
2020
});
2121

2222
it('should load a simple plugin', async () => {
23-
const plugin: PluginDefinition = {
23+
const plugin: RegistryPluginDefinition = {
2424
name: 'test-plugin',
2525
version: '1.0.0',
2626
register: (reg) => {
@@ -38,7 +38,7 @@ describe('PluginSystem', () => {
3838

3939
it('should execute onLoad lifecycle hook', async () => {
4040
const onLoad = vi.fn();
41-
const plugin: PluginDefinition = {
41+
const plugin: RegistryPluginDefinition = {
4242
name: 'test-plugin',
4343
version: '1.0.0',
4444
register: () => {},
@@ -52,7 +52,7 @@ describe('PluginSystem', () => {
5252

5353
it('should execute async onLoad lifecycle hook', async () => {
5454
const onLoad = vi.fn().mockResolvedValue(undefined);
55-
const plugin: PluginDefinition = {
55+
const plugin: RegistryPluginDefinition = {
5656
name: 'test-plugin',
5757
version: '1.0.0',
5858
register: () => {},
@@ -66,7 +66,7 @@ describe('PluginSystem', () => {
6666

6767
it('should not load plugin twice', async () => {
6868
const onLoad = vi.fn();
69-
const plugin: PluginDefinition = {
69+
const plugin: RegistryPluginDefinition = {
7070
name: 'test-plugin',
7171
version: '1.0.0',
7272
register: () => {},
@@ -80,7 +80,7 @@ describe('PluginSystem', () => {
8080
});
8181

8282
it('should check dependencies before loading', async () => {
83-
const plugin: PluginDefinition = {
83+
const plugin: RegistryPluginDefinition = {
8484
name: 'dependent-plugin',
8585
version: '1.0.0',
8686
dependencies: ['base-plugin'],
@@ -93,13 +93,13 @@ describe('PluginSystem', () => {
9393
});
9494

9595
it('should load plugins with dependencies in correct order', async () => {
96-
const basePlugin: PluginDefinition = {
96+
const basePlugin: RegistryPluginDefinition = {
9797
name: 'base-plugin',
9898
version: '1.0.0',
9999
register: () => {}
100100
};
101101

102-
const dependentPlugin: PluginDefinition = {
102+
const dependentPlugin: RegistryPluginDefinition = {
103103
name: 'dependent-plugin',
104104
version: '1.0.0',
105105
dependencies: ['base-plugin'],
@@ -115,7 +115,7 @@ describe('PluginSystem', () => {
115115

116116
it('should unload a plugin', async () => {
117117
const onUnload = vi.fn();
118-
const plugin: PluginDefinition = {
118+
const plugin: RegistryPluginDefinition = {
119119
name: 'test-plugin',
120120
version: '1.0.0',
121121
register: () => {},
@@ -132,13 +132,13 @@ describe('PluginSystem', () => {
132132
});
133133

134134
it('should prevent unloading plugin with dependents', async () => {
135-
const basePlugin: PluginDefinition = {
135+
const basePlugin: RegistryPluginDefinition = {
136136
name: 'base-plugin',
137137
version: '1.0.0',
138138
register: () => {}
139139
};
140140

141-
const dependentPlugin: PluginDefinition = {
141+
const dependentPlugin: RegistryPluginDefinition = {
142142
name: 'dependent-plugin',
143143
version: '1.0.0',
144144
dependencies: ['base-plugin'],
@@ -160,7 +160,7 @@ describe('PluginSystem', () => {
160160
});
161161

162162
it('should get plugin definition', async () => {
163-
const plugin: PluginDefinition = {
163+
const plugin: RegistryPluginDefinition = {
164164
name: 'test-plugin',
165165
version: '1.0.0',
166166
register: () => {}
@@ -173,13 +173,13 @@ describe('PluginSystem', () => {
173173
});
174174

175175
it('should get all plugins', async () => {
176-
const plugin1: PluginDefinition = {
176+
const plugin1: RegistryPluginDefinition = {
177177
name: 'plugin-1',
178178
version: '1.0.0',
179179
register: () => {}
180180
};
181181

182-
const plugin2: PluginDefinition = {
182+
const plugin2: RegistryPluginDefinition = {
183183
name: 'plugin-2',
184184
version: '1.0.0',
185185
register: () => {}
@@ -196,7 +196,7 @@ describe('PluginSystem', () => {
196196

197197
it('should call register function with registry', async () => {
198198
const registerFn = vi.fn();
199-
const plugin: PluginDefinition = {
199+
const plugin: RegistryPluginDefinition = {
200200
name: 'test-plugin',
201201
version: '1.0.0',
202202
register: registerFn
@@ -210,7 +210,7 @@ describe('PluginSystem', () => {
210210
});
211211

212212
it('should cleanup on registration failure', async () => {
213-
const plugin: PluginDefinition = {
213+
const plugin: RegistryPluginDefinition = {
214214
name: 'failing-plugin',
215215
version: '1.0.0',
216216
register: () => {

packages/core/src/utils/managedBy.test.ts

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,56 @@
11
import { describe, it, expect } from 'vitest';
22
import { MANAGED_BY_BUCKETS } from '@object-ui/types';
33
import {
4-
resolveCrudAffordances,
4+
resolveEffectiveCrudAffordances,
55
isWriteOptedIn,
66
isSystemWritable,
77
isObjectInlineEditable,
88
normalizeUserAction,
99
userActionPredicates,
1010
} from './managedBy';
1111

12-
describe('resolveCrudAffordances (shared source of truth)', () => {
12+
describe('resolveEffectiveCrudAffordances — bucket half, delegated to the spec', () => {
1313
it('defaults to the platform bucket (full CRUD) when managedBy is unset', () => {
14-
expect(resolveCrudAffordances({})).toEqual({
14+
expect(resolveEffectiveCrudAffordances({})).toEqual({
1515
create: true, import: true, edit: true, delete: true, exportCsv: true,
1616
});
17-
expect(resolveCrudAffordances(null)).toEqual(resolveCrudAffordances({ managedBy: 'platform' }));
17+
expect(resolveEffectiveCrudAffordances(null)).toEqual(resolveEffectiveCrudAffordances({ managedBy: 'platform' }));
1818
});
1919

2020
it('config: New/Edit/Delete + export, no import', () => {
21-
expect(resolveCrudAffordances({ managedBy: 'config' })).toEqual({
21+
expect(resolveEffectiveCrudAffordances({ managedBy: 'config' })).toEqual({
2222
create: true, import: false, edit: true, delete: true, exportCsv: true,
2323
});
2424
});
2525

2626
it('system / engine-owned / append-only / better-auth: export-only by default', () => {
2727
for (const managedBy of ['system', 'engine-owned', 'append-only', 'better-auth']) {
28-
expect(resolveCrudAffordances({ managedBy })).toEqual({
28+
expect(resolveEffectiveCrudAffordances({ managedBy })).toEqual({
2929
create: false, import: false, edit: false, delete: false, exportCsv: true,
3030
});
3131
}
3232
});
3333

3434
it('userActions overrides the bucket default (ADR-0103 writable system)', () => {
35-
const aff = resolveCrudAffordances({ managedBy: 'system', userActions: { create: true, edit: true, delete: true } });
35+
const aff = resolveEffectiveCrudAffordances({ managedBy: 'system', userActions: { create: true, edit: true, delete: true } });
3636
expect(aff).toMatchObject({ create: true, edit: true, delete: true, import: false });
3737
});
3838

3939
it('unknown bucket falls back to platform (defensive)', () => {
40-
expect(resolveCrudAffordances({ managedBy: 'totally-unknown' }).edit).toBe(true);
40+
expect(resolveEffectiveCrudAffordances({ managedBy: 'totally-unknown' }).edit).toBe(true);
4141
});
4242

4343
it('#2614 object form: carries predicates, keys off enabled, boolean path unchanged', () => {
44-
const withPreds = resolveCrudAffordances({
44+
const withPreds = resolveEffectiveCrudAffordances({
4545
managedBy: 'platform',
4646
userActions: { edit: { enabled: true, disabledWhen: 'record.locked == true' } },
4747
});
4848
expect(withPreds.edit).toBe(true);
4949
expect(withPreds.editPredicates).toEqual({ disabledWhen: 'record.locked == true' });
5050
// enabled omitted → falls back to the bucket default (platform edit = true)
51-
expect(resolveCrudAffordances({ managedBy: 'platform', userActions: { edit: { disabledWhen: 'x' } } }).edit).toBe(true);
51+
expect(resolveEffectiveCrudAffordances({ managedBy: 'platform', userActions: { edit: { disabledWhen: 'x' } } }).edit).toBe(true);
5252
// boolean form leaves predicates absent
53-
expect(resolveCrudAffordances({ managedBy: 'system', userActions: { edit: true } }).editPredicates).toBeUndefined();
53+
expect(resolveEffectiveCrudAffordances({ managedBy: 'system', userActions: { edit: true } }).editPredicates).toBeUndefined();
5454
});
5555
});
5656

@@ -144,26 +144,26 @@ describe('MANAGED_BY_BUCKETS', () => {
144144
});
145145
});
146146

147-
describe('resolveCrudAffordances — effective API operations (#3391)', () => {
147+
describe('resolveEffectiveCrudAffordances — effective API operations (#3391)', () => {
148148
it('undefined effective set → affordances unchanged (backward-compatible)', () => {
149149
const platform = { managedBy: 'platform' };
150-
expect(resolveCrudAffordances(platform)).toEqual(resolveCrudAffordances(platform, undefined));
151-
expect(resolveCrudAffordances(platform, undefined)).toEqual({
150+
expect(resolveEffectiveCrudAffordances(platform)).toEqual(resolveEffectiveCrudAffordances(platform, undefined));
151+
expect(resolveEffectiveCrudAffordances(platform, undefined)).toEqual({
152152
create: true, import: true, edit: true, delete: true, exportCsv: true,
153153
});
154154
});
155155

156156
it('ANDs each affordance bit with its API operation (create/import→create/import, edit→update, delete→delete, exportCsv→export)', () => {
157157
// A full-CRUD platform object whose server effective set is read-only + list-derived.
158-
const aff = resolveCrudAffordances({ managedBy: 'platform' }, ['get', 'list', 'export']);
158+
const aff = resolveEffectiveCrudAffordances({ managedBy: 'platform' }, ['get', 'list', 'export']);
159159
expect(aff).toMatchObject({
160160
create: false, import: false, edit: false, delete: false, exportCsv: true,
161161
});
162162
});
163163

164164
it('keeps a bit only when BOTH the affordance and the effective op allow it', () => {
165165
// create+update present → create/import/edit survive; no delete/list → delete/export drop.
166-
const aff = resolveCrudAffordances({ managedBy: 'platform' }, ['create', 'update', 'import']);
166+
const aff = resolveEffectiveCrudAffordances({ managedBy: 'platform' }, ['create', 'update', 'import']);
167167
expect(aff.create).toBe(true);
168168
expect(aff.edit).toBe(true);
169169
expect(aff.import).toBe(true);
@@ -172,15 +172,15 @@ describe('resolveCrudAffordances — effective API operations (#3391)', () => {
172172
});
173173

174174
it('empty effective set → all bits off (deny-all)', () => {
175-
expect(resolveCrudAffordances({ managedBy: 'platform' }, [])).toMatchObject({
175+
expect(resolveEffectiveCrudAffordances({ managedBy: 'platform' }, [])).toMatchObject({
176176
create: false, import: false, edit: false, delete: false, exportCsv: false,
177177
});
178178
});
179179

180180
it('never re-enables a bit the bucket/userActions already denied', () => {
181181
// config bucket has no import; even if the server would allow import, the
182182
// UI-intent axis keeps it off (intersection, never union).
183-
const aff = resolveCrudAffordances({ managedBy: 'config' }, ['create', 'update', 'delete', 'import', 'export']);
183+
const aff = resolveEffectiveCrudAffordances({ managedBy: 'config' }, ['create', 'update', 'delete', 'import', 'export']);
184184
expect(aff.import).toBe(false); // config never imports
185185
expect(aff.create).toBe(true);
186186
expect(aff.exportCsv).toBe(true);

0 commit comments

Comments
 (0)