@elytracms/studio 0.0.80 → 0.0.81
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/convex/contract-parity.test.ts +1 -1
- package/convex/functions.ts +13 -3
- package/convex/http-routes.test.ts +143 -0
- package/convex/http.ts +19 -9
- package/convex/schema.ts +0 -19
- package/convex/users.ts +1 -1
- package/dist/components/app-views/default-modules.d.ts +141 -44
- package/dist/components/app-views/default-modules.js +229 -65
- package/dist/components/app-views/default-modules.js.map +1 -1
- package/dist/components/app-views/module.d.ts +38 -0
- package/dist/components/app-views/module.js.map +1 -1
- package/dist/components/app-views/sidebar.d.ts +5 -2
- package/dist/components/app-views/sidebar.js +16 -5
- package/dist/components/app-views/sidebar.js.map +1 -1
- package/dist/components/app-views/studio-config.d.ts +27 -0
- package/dist/components/app-views/studio-config.js.map +1 -1
- package/dist/components/builder/canvas-render-context.js +7 -1
- package/dist/components/builder/canvas-render-context.js.map +1 -1
- package/dist/components/builder/reuse-context.js +7 -1
- package/dist/components/builder/reuse-context.js.map +1 -1
- package/dist/components/builder/reuse.d.ts +94 -16
- package/dist/components/builder/reuse.js +201 -19
- package/dist/components/builder/reuse.js.map +1 -1
- package/dist/components/cms/bulk-actions.js +3 -1
- package/dist/components/cms/bulk-actions.js.map +1 -1
- package/dist/components/cms/cell-edit.js +6 -2
- package/dist/components/cms/cell-edit.js.map +1 -1
- package/dist/components/cms/cms-store.d.ts +40 -13
- package/dist/components/cms/cms-store.js +50 -22
- package/dist/components/cms/cms-store.js.map +1 -1
- package/dist/components/cms/collection-table.js +2 -2
- package/dist/components/cms/collection-table.js.map +1 -1
- package/dist/components/cms/detail-editor.js +11 -5
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/field-control.js +2 -1
- package/dist/components/cms/field-control.js.map +1 -1
- package/dist/components/cms/field-renderer.js +8 -1
- package/dist/components/cms/field-renderer.js.map +1 -1
- package/dist/components/cms/import-export.js +2 -1
- package/dist/components/cms/import-export.js.map +1 -1
- package/dist/components/cms/issues-pane.d.ts +4 -2
- package/dist/components/cms/issues-pane.js +6 -4
- package/dist/components/cms/issues-pane.js.map +1 -1
- package/dist/components/components/transfer-dialog.js +10 -12
- package/dist/components/components/transfer-dialog.js.map +1 -1
- package/dist/components/project-list.d.ts +9 -3
- package/dist/components/project-list.js +11 -5
- package/dist/components/project-list.js.map +1 -1
- package/dist/components/project-switcher.d.ts +3 -1
- package/dist/components/project-switcher.js +4 -2
- package/dist/components/project-switcher.js.map +1 -1
- package/dist/components/redirects/redirects-store.d.ts +21 -0
- package/dist/components/redirects/redirects-store.js +17 -4
- package/dist/components/redirects/redirects-store.js.map +1 -1
- package/dist/components/section-nav.js +102 -4
- package/dist/components/section-nav.js.map +1 -1
- package/dist/customization.d.ts +7 -0
- package/dist/customization.js.map +1 -1
- package/dist/lib/data/collection-content.d.ts +6 -1
- package/dist/lib/data/collection-content.js +50 -12
- package/dist/lib/data/collection-content.js.map +1 -1
- package/dist/lib/i18n/messages.de.js +132 -0
- package/dist/lib/i18n/messages.de.js.map +1 -1
- package/dist/lib/i18n/messages.en.d.ts +103 -0
- package/dist/lib/i18n/messages.en.js +146 -0
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/lib/locale/binding-fallback.d.ts +8 -1
- package/dist/lib/locale/binding-fallback.js +8 -1
- package/dist/lib/locale/binding-fallback.js.map +1 -1
- package/dist/lib/locale/locale-store.d.ts +41 -9
- package/dist/lib/locale/locale-store.js +34 -15
- package/dist/lib/locale/locale-store.js.map +1 -1
- package/dist/lib/locale/locale-switcher.d.ts +3 -1
- package/dist/lib/locale/locale-switcher.js +4 -2
- package/dist/lib/locale/locale-switcher.js.map +1 -1
- package/dist/lib/locale/use-studio-locale.d.ts +9 -4
- package/dist/lib/locale/use-studio-locale.js.map +1 -1
- package/dist/lib/persistence/convex-adapter.d.ts +1 -3
- package/dist/lib/persistence/convex-adapter.js +0 -55
- package/dist/lib/persistence/convex-adapter.js.map +1 -1
- package/dist/lib/persona/labels.d.ts +42 -1
- package/dist/lib/persona/labels.js +57 -0
- package/dist/lib/persona/labels.js.map +1 -1
- package/dist/lib/routes/route-issues.d.ts +9 -0
- package/dist/lib/routes/route-issues.js +117 -0
- package/dist/lib/routes/route-issues.js.map +1 -0
- package/dist/lib/workspace/config-issues.d.ts +54 -0
- package/dist/lib/workspace/config-issues.js +70 -0
- package/dist/lib/workspace/config-issues.js.map +1 -0
- package/dist/lib/workspace/index.d.ts +9 -0
- package/dist/lib/workspace/index.js +11 -0
- package/dist/lib/workspace/index.js.map +1 -1
- package/dist/lib/workspace/switcher-source.d.ts +20 -3
- package/dist/lib/workspace/switcher-source.js.map +1 -1
- package/package.json +2 -2
- package/convex/cliTokens.ts +0 -63
- package/convex/sync.ts +0 -297
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect } from 'react';
|
|
3
3
|
import { Blocks, Database, FileCog, Image, LayoutGrid, Puzzle, Settings as SettingsIcon, Signpost, Users, } from 'lucide-react';
|
|
4
|
-
import { assetCollection, extendCollectionDef, isSingleton, resolveSingletonDocument,
|
|
5
|
-
import { reconcileSeedDocumentLocalization } from '@elytracms/core/persistence';
|
|
4
|
+
import { assetCollection, extendCollectionDef, isSingleton, resolveSingletonDocument, } from '@elytracms/core/cms-core';
|
|
6
5
|
import { NavigateOnce, STUDIO_PROJECT_ROUTE, studioPath } from '@elytracms/studio/router';
|
|
7
6
|
import { Text } from '@elytracms/studio/ui';
|
|
8
7
|
import { localizedLabel, translate } from '@elytracms/studio/lib/i18n';
|
|
9
8
|
import { getInstantiatedKit } from '@elytracms/studio/lib/starters/kit-session';
|
|
10
|
-
import { isPlaygroundMode } from '@elytracms/studio/lib/persistence';
|
|
11
9
|
import { resolveDeploymentCollections, resolveDeploymentLocaleConfig } from '@elytracms/studio/lib/workspace';
|
|
12
10
|
import { cmsDeepLink, componentsDeepLink, usersDeepLink, redirectsDeepLink, assetsDeepLink, groupNode, separatorNode, viewNode, } from './module.js';
|
|
13
11
|
import { SectionSurface } from '../section-surface.js';
|
|
@@ -32,29 +30,95 @@ import { OperationErrorBanner } from '../operation-error.js';
|
|
|
32
30
|
function slugify(label) {
|
|
33
31
|
return label.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'view';
|
|
34
32
|
}
|
|
35
|
-
/**
|
|
36
|
-
|
|
33
|
+
/**
|
|
34
|
+
* The auto-parent namespace for a ONE-ITEM `collections` group (EC-468).
|
|
35
|
+
*
|
|
36
|
+
* A PREFIX, not a suffix, and deliberately so: every other derived CMS id starts with
|
|
37
|
+
* `cms:`, so the 4th character (`-` here, `:` there) makes the two namespaces disjoint
|
|
38
|
+
* BY CONSTRUCTION — no collection id, whatever it contains, can forge a `cms-group:`
|
|
39
|
+
* id, whereas a suffix marker (`cms:page:group`) would be forged by a collection
|
|
40
|
+
* literally named `page:group` (`collectionDefSchema.id` is `z.string().min(1)` —
|
|
41
|
+
* nothing reserves `:` or `-`).
|
|
42
|
+
*/
|
|
43
|
+
const CMS_GROUP_ID_PREFIX = 'cms-group:';
|
|
44
|
+
/**
|
|
45
|
+
* Derive a stable, deterministic id from the CMS focus (overridable via `config.id`).
|
|
46
|
+
*
|
|
47
|
+
* The id is the route segment, the rail's active-state key AND the override key
|
|
48
|
+
* (EC-311), so it must be injective: two focuses deriving one id means
|
|
49
|
+
* `resolveOverrides` drops the later view. The derived shapes:
|
|
50
|
+
*
|
|
51
|
+
* | focus | id |
|
|
52
|
+
* | ---------------------------- | ------------------------- |
|
|
53
|
+
* | none | `cms` |
|
|
54
|
+
* | `collection: 'page'` (leaf) | `cms:page` |
|
|
55
|
+
* | `collections: ['a','b']` | `cms:a-b` |
|
|
56
|
+
* | `collections: ['page']` | `cms-group:page` |
|
|
57
|
+
*
|
|
58
|
+
* EC-468 — why the one-item group is its own namespace: `cms:${join(L)}` of a
|
|
59
|
+
* single-element list IS that element, so the auto-generated PARENT took the very id
|
|
60
|
+
* its own auto-expanded child derives (`cmsCollectionLeaf` → `cmsView({ collection })`).
|
|
61
|
+
* Both landed in the one flat override list `resolveSidebar` feeds `resolveOverrides`,
|
|
62
|
+
* so the child was dropped with a `duplicate-override-id` error and the parent rendered
|
|
63
|
+
* childless — a forced collision no config could avoid. Nothing else changes: the leaf
|
|
64
|
+
* and the multi-collection join keep their exact ids (consumer rails route on them).
|
|
65
|
+
*
|
|
66
|
+
* EC-477 — `-` is a SEPARATOR here, and a collection id may legally contain one, so a
|
|
67
|
+
* collection literally named `a-b` derives the same `cms:a-b` as a `['a','b']` group.
|
|
68
|
+
* That ambiguity is NOT closed by constraining collection ids (a permanent, repo-wide
|
|
69
|
+
* naming ban to fix a two-config coincidence, when hyphenated ids are ordinary — see
|
|
70
|
+
* the task's Done); it is DETECTED where both facts are known, by
|
|
71
|
+
* {@link checkSidebarIdKeyspace}, which raises a `derived-id-collision` the moment a
|
|
72
|
+
* group's join equals a real collection id. The escape hatch is unchanged: an explicit
|
|
73
|
+
* `nav.cms({ id })` always wins, and the derivation below is untouched — consumer rails
|
|
74
|
+
* route on these ids.
|
|
75
|
+
*/
|
|
76
|
+
export function cmsViewId(config) {
|
|
37
77
|
if (config.id)
|
|
38
78
|
return config.id;
|
|
39
79
|
if (config.collection)
|
|
40
80
|
return `cms:${config.collection}`;
|
|
41
|
-
|
|
42
|
-
|
|
81
|
+
const collections = config.collections;
|
|
82
|
+
if (collections && collections.length > 0) {
|
|
83
|
+
if (collections.length === 1)
|
|
84
|
+
return `${CMS_GROUP_ID_PREFIX}${collections[0]}`;
|
|
85
|
+
return `cms:${joinCollectionIds(collections)}`;
|
|
43
86
|
}
|
|
44
87
|
return 'cms';
|
|
45
88
|
}
|
|
89
|
+
/** The multi-collection focus join — ONE definition, so the id and the EC-477 check cannot drift. */
|
|
90
|
+
function joinCollectionIds(collections) {
|
|
91
|
+
return [...collections].join('-');
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The id provenance a CMS focus carries for diagnostics (EC-477) — set only for the one
|
|
95
|
+
* derived shape that is genuinely ambiguous: a MULTI-collection join. An explicit `id`,
|
|
96
|
+
* a single-collection leaf and the one-item `cms-group:` parent are all unambiguous by
|
|
97
|
+
* construction (EC-468), so they carry nothing and are never diagnosed.
|
|
98
|
+
*/
|
|
99
|
+
function cmsDerivedId(config) {
|
|
100
|
+
if (config.id || config.collection)
|
|
101
|
+
return undefined;
|
|
102
|
+
const collections = config.collections;
|
|
103
|
+
if (!collections || collections.length < 2)
|
|
104
|
+
return undefined;
|
|
105
|
+
return { from: 'collection-join', collections: [...collections] };
|
|
106
|
+
}
|
|
46
107
|
/**
|
|
47
108
|
* Build a CMS view — the default `cms` view, or a focused instance. Every instance
|
|
48
109
|
* shares one surface ({@link CmsScreen}); `collections` narrows the sidebar + index
|
|
49
110
|
* to a subset, `collection` is a single-collection lens that opens straight into
|
|
50
111
|
* that collection's list. The id is derived from the focus (deterministic, stable
|
|
51
|
-
* across rail reorders) unless `id` is given —
|
|
112
|
+
* across rail reorders) unless `id` is given — see {@link cmsViewId} for the derived
|
|
113
|
+
* shapes and the one keyspace ambiguity that survives.
|
|
52
114
|
*/
|
|
53
115
|
export function cmsView(config = {}) {
|
|
54
116
|
const single = config.collection;
|
|
55
117
|
const focus = single ? [single] : config.collections;
|
|
118
|
+
const derivedId = cmsDerivedId(config);
|
|
56
119
|
return {
|
|
57
120
|
id: cmsViewId(config),
|
|
121
|
+
...(derivedId ? { derivedId } : {}),
|
|
58
122
|
// Lazy label/description (EC-241): the sidebar is built when the consumer's
|
|
59
123
|
// `studio.config` module loads — BEFORE `createStudio` seeds the i18n config — so
|
|
60
124
|
// resolving here would bake English. A getter defers resolution to render, when
|
|
@@ -255,10 +319,22 @@ export function builderView() {
|
|
|
255
319
|
}, replace: true })),
|
|
256
320
|
};
|
|
257
321
|
}
|
|
258
|
-
/**
|
|
322
|
+
/**
|
|
323
|
+
* Build a fully custom view (the `nav.view(...)` escape hatch).
|
|
324
|
+
*
|
|
325
|
+
* With no `id` the id is a SLUG of the label, and that slug carries a
|
|
326
|
+
* {@link DerivedViewId} marker (EC-476): a label is a display string a developer picks
|
|
327
|
+
* freely, so `label: 'CMS'` deriving `cms` is a coincidence, not a request to replace
|
|
328
|
+
* the built-in CMS surface. {@link checkSidebarIdKeyspace} refuses the takeover and
|
|
329
|
+
* says so; an explicit `id` carries no marker and keeps the documented replacement.
|
|
330
|
+
*/
|
|
259
331
|
export function customView(config) {
|
|
332
|
+
const derivedId = config.id
|
|
333
|
+
? undefined
|
|
334
|
+
: { from: 'label', label: config.label };
|
|
260
335
|
return {
|
|
261
336
|
id: config.id ?? slugify(config.label),
|
|
337
|
+
...(derivedId ? { derivedId } : {}),
|
|
262
338
|
label: config.label,
|
|
263
339
|
description: config.description ?? config.label,
|
|
264
340
|
icon: config.icon ?? LayoutGrid,
|
|
@@ -284,6 +360,97 @@ export const DEFAULT_VIEWS = [
|
|
|
284
360
|
settingsView(),
|
|
285
361
|
builderView(),
|
|
286
362
|
];
|
|
363
|
+
/**
|
|
364
|
+
* Every id the engine already owns — the replacement targets of the sidebar seam,
|
|
365
|
+
* INCLUDING the hidden `builder` redirect (it is in the keyspace even though the stock
|
|
366
|
+
* rail does not list it, so a derived id can swallow it just the same).
|
|
367
|
+
*/
|
|
368
|
+
export const BUILTIN_VIEW_IDS = new Set(DEFAULT_VIEWS.map((view) => view.id));
|
|
369
|
+
/** The extension point these issues are located at — the same literal `resolveSidebar` stamps. */
|
|
370
|
+
const SIDEBAR_POINT = 'sidebar';
|
|
371
|
+
/**
|
|
372
|
+
* The catalog keys for the two keyspace findings (EC-478). This module stays pure —
|
|
373
|
+
* it names a key and its identifier values; `lib/persona/labels.ts` resolves them at
|
|
374
|
+
* the render site, so the rail foot localizes as one list.
|
|
375
|
+
*/
|
|
376
|
+
const KEYSPACE_KEYS = {
|
|
377
|
+
builtinCollision: 'customization.issue.derivedIdBuiltinCollision',
|
|
378
|
+
collectionCollision: 'customization.issue.derivedIdCollectionCollision',
|
|
379
|
+
};
|
|
380
|
+
/**
|
|
381
|
+
* EC-476/477 — guard the sidebar id keyspace against ids that were DERIVED rather than
|
|
382
|
+
* declared, and landed somewhere that already means something.
|
|
383
|
+
*
|
|
384
|
+
* The seam's replacement rule is id equality (`resolveOverrides`: an entry whose id is a
|
|
385
|
+
* default's id replaces it). That is exactly right for an id the developer TYPED, and
|
|
386
|
+
* exactly wrong for one a builder computed — and the generic core cannot tell them
|
|
387
|
+
* apart, because by the time an entry reaches it, an id is just a string. So the rule
|
|
388
|
+
* lives HERE, next to the derivations that produce it ({@link slugify},
|
|
389
|
+
* {@link cmsViewId}) and next to {@link DEFAULT_VIEWS}, the set it has to protect.
|
|
390
|
+
*
|
|
391
|
+
* Two derived shapes, two different remedies — because the cost of being wrong differs:
|
|
392
|
+
*
|
|
393
|
+
* - **A slugged LABEL landing on a built-in id (EC-476) is REJECTED** (`error`). Losing
|
|
394
|
+
* a whole built-in surface — the CMS, Assets, the users roster — to a coincidence of
|
|
395
|
+
* spelling is far worse than one custom view not appearing, and the developer's fix is
|
|
396
|
+
* a one-line explicit `id`. The built-in is kept, so nothing disappears.
|
|
397
|
+
* - **A collection JOIN equal to a real collection id (EC-477) is KEPT** (`warning`).
|
|
398
|
+
* Nothing here is a takeover: the group's id is legitimate, it is merely no longer
|
|
399
|
+
* unique to it, so dropping the view would destroy a rail item the developer
|
|
400
|
+
* explicitly declared in order to report a naming clash. Reporting is the whole fix.
|
|
401
|
+
*
|
|
402
|
+
* Pure and deterministic — the house "extract the core, test the core" shape.
|
|
403
|
+
* `builtinIds` and `collections` are parameters so the rule is testable against a
|
|
404
|
+
* synthetic keyspace, not just the live one.
|
|
405
|
+
*/
|
|
406
|
+
export function checkSidebarIdKeyspace(views, collections = [], builtinIds = BUILTIN_VIEW_IDS) {
|
|
407
|
+
const collectionIds = new Set(collections.map((collection) => collection.id));
|
|
408
|
+
const kept = [];
|
|
409
|
+
const issues = [];
|
|
410
|
+
for (const view of views) {
|
|
411
|
+
const derived = view.derivedId;
|
|
412
|
+
// EC-476 — an auto-derived id must never claim a built-in surface.
|
|
413
|
+
if (derived?.from === 'label' && builtinIds.has(view.id)) {
|
|
414
|
+
issues.push({
|
|
415
|
+
code: 'derived-id-collision',
|
|
416
|
+
severity: 'error',
|
|
417
|
+
message: `View "${derived.label}" for "${SIDEBAR_POINT}" derives the id "${view.id}" from its label, which is the built-in "${view.id}" view — the built-in is kept and this view is ignored. Pass an explicit id to replace a built-in on purpose.`,
|
|
418
|
+
messageRef: {
|
|
419
|
+
key: KEYSPACE_KEYS.builtinCollision,
|
|
420
|
+
vars: { label: derived.label, point: SIDEBAR_POINT, id: view.id },
|
|
421
|
+
},
|
|
422
|
+
point: SIDEBAR_POINT,
|
|
423
|
+
id: view.id,
|
|
424
|
+
});
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
// EC-477 — a multi-collection join that a single collection could equally have produced.
|
|
428
|
+
if (derived?.from === 'collection-join' && collectionIds.has(joinCollectionIds(derived.collections))) {
|
|
429
|
+
issues.push({
|
|
430
|
+
code: 'derived-id-collision',
|
|
431
|
+
severity: 'warning',
|
|
432
|
+
message: `View id "${view.id}" for "${SIDEBAR_POINT}" is ambiguous: it joins the collections ${quotedIds(derived.collections)} with "-", and a collection is literally named "${joinCollectionIds(derived.collections)}" — its own lens derives the same id. Pass an explicit id to this group.`,
|
|
433
|
+
messageRef: {
|
|
434
|
+
key: KEYSPACE_KEYS.collectionCollision,
|
|
435
|
+
vars: {
|
|
436
|
+
id: view.id,
|
|
437
|
+
point: SIDEBAR_POINT,
|
|
438
|
+
collections: quotedIds(derived.collections),
|
|
439
|
+
joined: joinCollectionIds(derived.collections),
|
|
440
|
+
},
|
|
441
|
+
},
|
|
442
|
+
point: SIDEBAR_POINT,
|
|
443
|
+
id: view.id,
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
kept.push(view);
|
|
447
|
+
}
|
|
448
|
+
return { views: kept, issues };
|
|
449
|
+
}
|
|
450
|
+
/** `"a", "b"` — a readable id list for an issue message (the EC-467 issue-copy shape). */
|
|
451
|
+
function quotedIds(ids) {
|
|
452
|
+
return ids.map((id) => `"${id}"`).join(', ');
|
|
453
|
+
}
|
|
287
454
|
/**
|
|
288
455
|
* The `nav` namespace — the single typed builder for `studio.config.tsx`'s section
|
|
289
456
|
* rail. Each call returns a {@link SidebarViewNode} (or a group / separator) that
|
|
@@ -313,6 +480,8 @@ export const nav = {
|
|
|
313
480
|
* The CMS view. `nav.cms()` / `nav.cms({ collections })` is a PARENT that nests its
|
|
314
481
|
* collections in the rail (each a focused `cms:<id>` lens); `nav.cms({ collection })`
|
|
315
482
|
* is a single-collection LEAF; `nav.cms({ children })` supplies explicit children.
|
|
483
|
+
* A one-item `collections` group is a parent whose id is `cms-group:<id>`, distinct
|
|
484
|
+
* from its single child by construction (EC-468 — see {@link cmsViewId}).
|
|
316
485
|
*/
|
|
317
486
|
cms: (config = {}) => {
|
|
318
487
|
const view = withNavPermission(cmsView(config), config);
|
|
@@ -360,59 +529,47 @@ function ComponentsScreen({ projectId, componentId, state, }) {
|
|
|
360
529
|
return (_jsxs("div", { className: "flex h-full min-h-0", children: [_jsx("aside", { className: "hidden h-full min-h-0 w-[300px] shrink-0 border-r border-border md:block", children: _jsx(RegistryBrowser, { projectId: projectId, activeComponentId: componentId ?? undefined }) }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto", children: componentId === null ? (_jsx(ComponentsIndex, { projectId: projectId })) : (_jsx(ComponentDetail, { projectId: projectId, componentId: componentId })) })] }));
|
|
361
530
|
}
|
|
362
531
|
/**
|
|
363
|
-
* The CMS schema source for a project mount
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
*
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
*
|
|
373
|
-
*
|
|
532
|
+
* The CMS schema source for a project mount: a hydrated session wins (Convex),
|
|
533
|
+
* then a kit's imported schema (EC-119), then the active project's
|
|
534
|
+
* config-declared collections (AD-11, `resolveDeploymentCollections`).
|
|
535
|
+
*
|
|
536
|
+
* EC-479 — every rung is the PROJECT's own schema and there is no rung after
|
|
537
|
+
* them. The config rung used to be gated on playground mode, because outside it
|
|
538
|
+
* the `undefined` return let `CmsProvider` fall through to `cms-core`'s
|
|
539
|
+
* `sampleFilterableCollections` fixture — the engine's demo schema, shown as the
|
|
540
|
+
* project's, offering `filterable` fields the project's real backend would
|
|
541
|
+
* reject. With that fallback retired the gate has nothing left to protect: the
|
|
542
|
+
* Convex adapter is itself constructed with `resolveDeploymentCollections()`
|
|
543
|
+
* (`lib/persistence/index.ts`), so config collections are what connected mode
|
|
544
|
+
* serves too. Dropping it also DISSOLVES the EC-467 two-source divergence — the
|
|
545
|
+
* section rail reads `resolveDeploymentCollections()` directly, and now so does
|
|
546
|
+
* the store's last rung, so the two can no longer disagree.
|
|
374
547
|
*/
|
|
375
|
-
export function resolveCmsInitialCollections(cms, kit
|
|
376
|
-
return cms?.collections ?? kit?.collections ??
|
|
548
|
+
export function resolveCmsInitialCollections(cms, kit) {
|
|
549
|
+
return cms?.collections ?? kit?.collections ?? resolveDeploymentCollections();
|
|
377
550
|
}
|
|
378
551
|
/**
|
|
379
|
-
* The CMS documents source for a project mount
|
|
380
|
-
*
|
|
381
|
-
*
|
|
382
|
-
* only, and only when a config project actually resolved collections — the
|
|
383
|
-
* engine's own `sampleDocuments` fixture RECONCILED against them.
|
|
552
|
+
* The CMS documents source for a project mount — the document-side twin of
|
|
553
|
+
* {@link resolveCmsInitialCollections}: a hydrated session wins (Convex), then a
|
|
554
|
+
* kit's imported documents, then **nothing**.
|
|
384
555
|
*
|
|
385
|
-
*
|
|
386
|
-
*
|
|
387
|
-
*
|
|
388
|
-
*
|
|
389
|
-
*
|
|
390
|
-
*
|
|
391
|
-
* did not
|
|
392
|
-
*
|
|
393
|
-
* project never actually wrote.
|
|
556
|
+
* EC-479 — the third rung used to be the engine's own `sampleDocuments` fixture,
|
|
557
|
+
* reconciled against the project's schema (EC-424). Reconciling it was already an
|
|
558
|
+
* admission that the content was foreign: EC-424 existed only because the demo
|
|
559
|
+
* declares `post.slug` localized while the shared fixture stores it plain, so the
|
|
560
|
+
* seeded posts carried standing validation noise no project had written. The
|
|
561
|
+
* honest fix is not a better reconciliation but no foreign content at all — a
|
|
562
|
+
* studio whose config did not arrive now shows an empty CMS and a visible issue
|
|
563
|
+
* at the rail, not six documents about Ada Lovelace wearing the project's schema.
|
|
394
564
|
*
|
|
395
|
-
*
|
|
396
|
-
*
|
|
397
|
-
*
|
|
398
|
-
*
|
|
399
|
-
*
|
|
400
|
-
* resolution `resolveCmsInitialCollections` returns, so the two stay in lockstep
|
|
401
|
-
* by construction; only reconciles when that resolved to a non-empty config
|
|
402
|
-
* schema (env-only playground, with nothing config-declared, is unaffected).
|
|
565
|
+
* The playground keeps its world through the ordinary path, not through here: its
|
|
566
|
+
* seeded in-memory adapter IS a persisted backend behind the operations seam
|
|
567
|
+
* (AD-17), so `CmsSessionGate` hydrates it and the first rung answers. What that
|
|
568
|
+
* adapter seeds is `persistence/seed.ts`'s business — EC-475 (project-defined seed
|
|
569
|
+
* content) owns it.
|
|
403
570
|
*/
|
|
404
|
-
export function resolveCmsInitialDocuments(cms, kit
|
|
405
|
-
|
|
406
|
-
return cms.documents;
|
|
407
|
-
if (kit)
|
|
408
|
-
return kit.documents;
|
|
409
|
-
if (!collections || collections.length === 0)
|
|
410
|
-
return undefined;
|
|
411
|
-
const collectionsById = new Map(collections.map((collection) => [collection.id, collection]));
|
|
412
|
-
return sampleDocuments.map((doc) => {
|
|
413
|
-
const collection = collectionsById.get(doc.collection);
|
|
414
|
-
return collection ? reconcileSeedDocumentLocalization(doc, collection) : doc;
|
|
415
|
-
});
|
|
571
|
+
export function resolveCmsInitialDocuments(cms, kit) {
|
|
572
|
+
return cms?.documents ?? kit?.documents;
|
|
416
573
|
}
|
|
417
574
|
/**
|
|
418
575
|
* The CMS locale config for a project mount (EC-357 finding #3). Locales are
|
|
@@ -421,18 +578,28 @@ export function resolveCmsInitialDocuments(cms, kit, collections = resolveCmsIni
|
|
|
421
578
|
* wiring only applied the project's locale settings on a HYDRATED session, so a
|
|
422
579
|
* playground boot — or a connected FIRST RUN with nothing persisted yet — fell
|
|
423
580
|
* back to a kit's locales or the core's sample `en/de` fixture and the switcher
|
|
424
|
-
* showed `EN·/DE` against a `locales: ['de']` config.
|
|
425
|
-
*
|
|
426
|
-
*
|
|
581
|
+
* showed `EN·/DE` against a `locales: ['de']` config.
|
|
582
|
+
*
|
|
583
|
+
* EC-479/EC-483 — the ladder is now TOTAL, and every rung is the project's own:
|
|
584
|
+
* config-declared locales, then (hydrated) its persisted settings, then a kit's,
|
|
585
|
+
* then its settings regardless. A `Project` only ever exists for a config-declared
|
|
586
|
+
* project (`studio-context.tsx`: `configProjects()`), so `project.settings` is
|
|
587
|
+
* config-derived too — which is what lets the CMS session require a locale instead
|
|
588
|
+
* of borrowing the engine's `en/de` fixture. The default locale is the literal
|
|
589
|
+
* storage key for every localized value, so a guessed one is a data migration.
|
|
427
590
|
* `configLocales` is a parameter purely so the ladder is unit-testable (same seam
|
|
428
591
|
* pattern as {@link resolveCmsInitialCollections}).
|
|
429
592
|
*/
|
|
430
593
|
export function resolveCmsInitialLocaleConfig(cms, kit, project, configLocales = resolveDeploymentLocaleConfig()) {
|
|
594
|
+
const projectSettings = {
|
|
595
|
+
default: project.settings.defaultLocale,
|
|
596
|
+
locales: [...project.settings.locales],
|
|
597
|
+
};
|
|
431
598
|
if (configLocales)
|
|
432
599
|
return configLocales;
|
|
433
600
|
if (cms)
|
|
434
|
-
return
|
|
435
|
-
return kit?.localeConfig;
|
|
601
|
+
return projectSettings;
|
|
602
|
+
return kit?.localeConfig ?? projectSettings;
|
|
436
603
|
}
|
|
437
604
|
/**
|
|
438
605
|
* CMS section: hydrates the persisted (or kit-seeded) collections + documents, then
|
|
@@ -450,10 +617,7 @@ function CmsScreen({ project, collectionId, documentId, state, field, focusColle
|
|
|
450
617
|
// A project created from a starter kit (EC-119) opens the CMS on the kit's schemas + content.
|
|
451
618
|
const kit = getInstantiatedKit(projectId);
|
|
452
619
|
return (_jsx(CmsSessionGate, { children: (cms) => {
|
|
453
|
-
|
|
454
|
-
// stays in lockstep with the SAME collections `initialCollections` uses.
|
|
455
|
-
const initialCollections = resolveCmsInitialCollections(cms, kit);
|
|
456
|
-
return (_jsx(CmsProvider, { projectId: projectId, initialCollections: initialCollections, initialDocuments: resolveCmsInitialDocuments(cms, kit, initialCollections), initialPublishedKeys: cms?.publishedKeys, initialTimestamps: cms?.timestamps, initialPublishTimestamps: cms?.publishTimestamps, initialChangedKeys: cms?.changedKeys, initialLocaleConfig: resolveCmsInitialLocaleConfig(cms, kit, project),
|
|
620
|
+
return (_jsx(CmsProvider, { projectId: projectId, initialCollections: resolveCmsInitialCollections(cms, kit), initialDocuments: resolveCmsInitialDocuments(cms, kit), initialPublishedKeys: cms?.publishedKeys, initialTimestamps: cms?.timestamps, initialPublishTimestamps: cms?.publishTimestamps, initialChangedKeys: cms?.changedKeys, initialLocaleConfig: resolveCmsInitialLocaleConfig(cms, kit, project),
|
|
457
621
|
// EC-461: a hydrated mount (cms non-null — both modes now) seeds the
|
|
458
622
|
// CURRENT persisted state, so staged docs are superseded by it; only
|
|
459
623
|
// a static kit/fixture seed lets staged entries win.
|