Skip to content

Commit 9a04d25

Browse files
os-zhuangclaude
andauthored
fix(registry): prefix every namespaced key exactly once, in every namespace (#3037)
#3023 fixed eleven record:* blocks registered as register('record:x', …, { namespace: 'record' }) — an already-prefixed name handed to a registry that prefixes it again, landing the block at record:record:x — and guarded that namespace alone. Twenty-two more were sitting in action: (5), element: (10) and page: (7), two of them (page:header, element:divider) curated public blocks. Checking one namespace is exactly what let them keep sitting there, so the guard now asks the whole registry rather than a prefix of it. Same fix as before: register the bare name and let `namespace` do the prefixing, with skipFallback: true so the fallback does not claim that bare name globally. It would otherwise take over `header`, `footer`, `sidebar`, `tabs`, `card`, `accordion`, `section`, `text`, `image`, `button`, `icon` — every one of which belongs to ui:. All 22 stay reachable exactly as <namespace>:<name>; the registry goes 522 keys to 500, and the contract is unchanged at 42/42. Found while probing why six curated Tier B primitives report no `inputs`. They do declare them — vitest.setup.dom.tsx registers simplified text / image / html / grid stubs that shadow the real registrations inside the test environment only. That shadowing is a separate question, left alone here; the doubled keys it turned up are not test-environment artifacts. Claude-Session: https://claude.ai/code/session_01N4mrr1ihhwnfEHFSWmGoMp Co-authored-by: Claude <noreply@anthropic.com>
1 parent fc60ad3 commit 9a04d25

13 files changed

Lines changed: 95 additions & 29 deletions

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
"@object-ui/components": patch
3+
"@object-ui/console": patch
4+
---
5+
6+
fix(registry): prefix every namespaced key exactly once, in every namespace
7+
8+
objectui#3023 fixed eleven `record:*` blocks registered as
9+
`register('record:x', …, { namespace: 'record' })` — an already-prefixed name
10+
handed to a registry that prefixes it again, landing the block at
11+
`record:record:x` — and guarded that namespace alone. Twenty-two more were
12+
sitting in `action:` (5), `element:` (10) and `page:` (7), two of them
13+
(`page:header`, `element:divider`) curated public blocks.
14+
15+
Checking one namespace is exactly what let them keep sitting there, so the
16+
guard now asks the whole registry rather than a prefix of it.
17+
18+
Same fix as before: register the bare name and let `namespace` do the
19+
prefixing, with `skipFallback: true` so the fallback does not claim that bare
20+
name globally. It would otherwise take over `header`, `footer`, `sidebar`,
21+
`tabs`, `card`, `accordion`, `section`, `text`, `image`, `button`, `icon`
22+
every one of which belongs to `ui:`. All 22 stay reachable exactly as
23+
`<namespace>:<name>`; the registry goes 522 keys to 500, and the contract is
24+
unchanged at 42/42.
25+
26+
Found while probing why six curated Tier B primitives report no `inputs`. They
27+
do declare them — `vitest.setup.dom.tsx` registers simplified `text` / `image` /
28+
`html` / `grid` stubs that shadow the real registrations inside the test
29+
environment only. That shadowing is a separate question, left alone here; the
30+
doubled keys it turned up are not test-environment artifacts.

apps/console/src/__tests__/public-contract.test.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -202,14 +202,7 @@ describe('PUBLIC_BLOCKS ↔ console coverage (reverse direction)', () => {
202202
});
203203

204204
it('registers each record:* block under one key, prefixed once', () => {
205-
// `register('record:x', …, { namespace: 'record' })` prefixes an
206-
// already-prefixed name: the block lands at `record:record:x` and stays
207-
// reachable only through the un-namespaced fallback, which happens to spell
208-
// `record:x`. Eleven blocks were registered that way. The contract hid it —
209-
// `getPublicConfigs()` rewrites `type` to the curated tag — so nothing
210-
// failed while the registry carried a phantom key per block.
211205
const keys = shippedRecordBlocks();
212-
expect(keys.filter((k) => k.startsWith(`${NS}:${NS}:`))).toEqual([]);
213206
expect(keys.every((k) => k.startsWith(`${NS}:`))).toBe(true);
214207
});
215208

@@ -224,6 +217,27 @@ describe('PUBLIC_BLOCKS ↔ console coverage (reverse direction)', () => {
224217
}
225218
});
226219

220+
it('prefixes every namespaced key exactly once, in every namespace', () => {
221+
// `register('page:header', …, { namespace: 'page' })` hands an
222+
// already-prefixed name to a registry that prefixes it again: the block
223+
// lands at `page:page:header` and stays reachable only through the
224+
// un-namespaced fallback, which happens to spell `page:header`. Nothing
225+
// fails — `getPublicConfigs()` rewrites `type` to the curated tag — so the
226+
// registry quietly carries a phantom key per block.
227+
//
228+
// objectui#3023 fixed the eleven in `record:` and guarded that namespace
229+
// alone. Twenty-two more were sitting in `action:`, `element:` and `page:`,
230+
// two of them (`page:header`, `element:divider`) curated public blocks.
231+
// Checking one namespace is what let them keep sitting there, so this asks
232+
// the whole registry.
233+
const doubled = ComponentRegistry.getKnownTypes().filter((k) => {
234+
const ns = ComponentRegistry.getMeta(k)?.namespace;
235+
return !!ns && k.startsWith(`${ns}:${ns}:`);
236+
});
237+
238+
expect(doubled).toEqual([]);
239+
});
240+
227241
it('keeps the chatter alias identical to the block it aliases', () => {
228242
// `record:chatter` is excluded because it duplicates `record:discussion`,
229243
// not because it is lesser. The moment the two configuration surfaces

packages/components/src/renderers/action/action-bar.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,8 +282,9 @@ const ActionBarRenderer = forwardRef<HTMLDivElement, { schema: ActionBarSchema;
282282

283283
ActionBarRenderer.displayName = 'ActionBarRenderer';
284284

285-
ComponentRegistry.register('action:bar', ActionBarRenderer, {
285+
ComponentRegistry.register('bar', ActionBarRenderer, {
286286
namespace: 'action',
287+
skipFallback: true,
287288
label: 'Action Bar',
288289
inputs: [
289290
{ name: 'actions', type: 'object', label: 'Actions' },

packages/components/src/renderers/action/action-button.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,9 @@ const ActionButtonRenderer = forwardRef<HTMLButtonElement, ActionButtonProps>(
175175

176176
ActionButtonRenderer.displayName = 'ActionButtonRenderer';
177177

178-
ComponentRegistry.register('action:button', ActionButtonRenderer, {
178+
ComponentRegistry.register('button', ActionButtonRenderer, {
179179
namespace: 'action',
180+
skipFallback: true,
180181
label: 'Action Button',
181182
inputs: [
182183
{ name: 'name', type: 'string', label: 'Action Name' },

packages/components/src/renderers/action/action-group.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,8 +294,9 @@ const ActionGroupRenderer = forwardRef<HTMLDivElement, { schema: ActionGroupSche
294294

295295
ActionGroupRenderer.displayName = 'ActionGroupRenderer';
296296

297-
ComponentRegistry.register('action:group', ActionGroupRenderer, {
297+
ComponentRegistry.register('group', ActionGroupRenderer, {
298298
namespace: 'action',
299+
skipFallback: true,
299300
label: 'Action Group',
300301
inputs: [
301302
{ name: 'name', type: 'string', label: 'Group Name' },

packages/components/src/renderers/action/action-icon.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,9 @@ const ActionIconRenderer = forwardRef<HTMLButtonElement, ActionIconProps>(
130130

131131
ActionIconRenderer.displayName = 'ActionIconRenderer';
132132

133-
ComponentRegistry.register('action:icon', ActionIconRenderer, {
133+
ComponentRegistry.register('icon', ActionIconRenderer, {
134134
namespace: 'action',
135+
skipFallback: true,
135136
label: 'Action Icon',
136137
inputs: [
137138
{ name: 'name', type: 'string', label: 'Action Name' },

packages/components/src/renderers/action/action-menu.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,9 @@ const ActionMenuRenderer = forwardRef<HTMLButtonElement, { schema: ActionMenuSch
220220

221221
ActionMenuRenderer.displayName = 'ActionMenuRenderer';
222222

223-
ComponentRegistry.register('action:menu', ActionMenuRenderer, {
223+
ComponentRegistry.register('menu', ActionMenuRenderer, {
224224
namespace: 'action',
225+
skipFallback: true,
225226
label: 'Action Menu',
226227
inputs: [
227228
{ name: 'label', type: 'string', label: 'Trigger Label' },

packages/components/src/renderers/basic/data-list.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,9 @@ function DefinitionListRenderer({ schema }: { schema: any }) {
7777
);
7878
}
7979

80-
ComponentRegistry.register('element:definition-list', DefinitionListRenderer, {
80+
ComponentRegistry.register('definition-list', DefinitionListRenderer, {
8181
namespace: 'element',
82+
skipFallback: true,
8283
label: 'Definition List',
8384
category: 'content',
8485
});
@@ -172,8 +173,9 @@ function RepeaterRenderer({ schema }: { schema: any }) {
172173
);
173174
}
174175

175-
ComponentRegistry.register('element:repeater', RepeaterRenderer, {
176+
ComponentRegistry.register('repeater', RepeaterRenderer, {
176177
namespace: 'element',
178+
skipFallback: true,
177179
label: 'Repeater',
178180
category: 'content',
179181
});

packages/components/src/renderers/basic/elements.tsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,9 @@ function ElementTextRenderer({ schema }: { schema: any }) {
9292
);
9393
}
9494

95-
ComponentRegistry.register('element:text', ElementTextRenderer, {
95+
ComponentRegistry.register('text', ElementTextRenderer, {
9696
namespace: 'element',
97+
skipFallback: true,
9798
label: 'Text',
9899
category: 'content',
99100
});
@@ -106,8 +107,9 @@ function ElementDividerRenderer({ schema }: { schema: any }) {
106107
return <Separator className={cn('my-4', schema?.className)} />;
107108
}
108109

109-
ComponentRegistry.register('element:divider', ElementDividerRenderer, {
110+
ComponentRegistry.register('divider', ElementDividerRenderer, {
110111
namespace: 'element',
112+
skipFallback: true,
111113
label: 'Divider',
112114
category: 'content',
113115
});
@@ -155,8 +157,9 @@ function ElementImageRenderer({ schema }: { schema: any }) {
155157
);
156158
}
157159

158-
ComponentRegistry.register('element:image', ElementImageRenderer, {
160+
ComponentRegistry.register('image', ElementImageRenderer, {
159161
namespace: 'element',
162+
skipFallback: true,
160163
label: 'Image',
161164
category: 'content',
162165
});
@@ -260,8 +263,9 @@ function ElementButtonRenderer({ schema }: { schema: any }) {
260263
);
261264
}
262265

263-
ComponentRegistry.register('element:button', ElementButtonRenderer, {
266+
ComponentRegistry.register('button', ElementButtonRenderer, {
264267
namespace: 'element',
268+
skipFallback: true,
265269
label: 'Button',
266270
category: 'action',
267271
});
@@ -381,8 +385,9 @@ function ElementNumberRenderer({ schema }: { schema: any }) {
381385
);
382386
}
383387

384-
ComponentRegistry.register('element:number', ElementNumberRenderer, {
388+
ComponentRegistry.register('number', ElementNumberRenderer, {
385389
namespace: 'element',
390+
skipFallback: true,
386391
label: 'Number',
387392
category: 'content',
388393
});

packages/components/src/renderers/basic/metadata-viewer.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,8 +379,9 @@ export function ElementMetadataViewerRenderer({ schema }: { schema: any }) {
379379
}
380380
}
381381

382-
ComponentRegistry.register('element:metadata_viewer', ElementMetadataViewerRenderer, {
382+
ComponentRegistry.register('metadata_viewer', ElementMetadataViewerRenderer, {
383383
namespace: 'element',
384+
skipFallback: true,
384385
label: 'Metadata Viewer',
385386
category: 'content',
386387
});

0 commit comments

Comments
 (0)