@elytracms/studio 0.0.86 → 0.0.88
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/asset-revalidate.test.ts +186 -0
- package/convex/assets.ts +53 -19
- package/convex/cms.ts +22 -0
- package/convex/delivery.ts +33 -116
- package/convex/functions.ts +3 -3
- package/convex/rail-write-parity.test.ts +180 -0
- package/convex/redirects.ts +33 -11
- package/convex/revalidate.ts +7 -5
- package/convex/triggers.ts +53 -3
- package/dist/components/ai/ai-chat-panel.js +8 -1
- package/dist/components/ai/ai-chat-panel.js.map +1 -1
- package/dist/components/app-views/default-modules.d.ts +20 -20
- package/dist/components/app-views/default-modules.js +27 -48
- package/dist/components/app-views/default-modules.js.map +1 -1
- package/dist/components/app-views/studio-config.d.ts +5 -6
- package/dist/components/app-views/studio-config.js.map +1 -1
- package/dist/components/assets/asset-metadata.d.ts +28 -19
- package/dist/components/assets/asset-metadata.js +24 -31
- package/dist/components/assets/asset-metadata.js.map +1 -1
- package/dist/components/assets/asset-selection.d.ts +24 -5
- package/dist/components/assets/asset-selection.js +39 -9
- package/dist/components/assets/asset-selection.js.map +1 -1
- package/dist/components/assets/assets-collection-view-source.d.ts +17 -14
- package/dist/components/assets/assets-collection-view-source.js +25 -31
- package/dist/components/assets/assets-collection-view-source.js.map +1 -1
- package/dist/components/assets/assets-store.d.ts +11 -1
- package/dist/components/assets/assets-store.js +10 -0
- package/dist/components/assets/assets-store.js.map +1 -1
- package/dist/components/assets/focal-point.d.ts +0 -7
- package/dist/components/assets/focal-point.js +0 -10
- package/dist/components/assets/focal-point.js.map +1 -1
- package/dist/components/builder/blocks-panel.js +3 -3
- package/dist/components/builder/blocks-panel.js.map +1 -1
- package/dist/components/builder/builder-store.d.ts +25 -1
- package/dist/components/builder/builder-store.js +3 -2
- package/dist/components/builder/builder-store.js.map +1 -1
- package/dist/components/builder/canvas-chrome-css.js +0 -17
- package/dist/components/builder/canvas-chrome-css.js.map +1 -1
- package/dist/components/builder/canvas-chrome.d.ts +10 -12
- package/dist/components/builder/canvas-chrome.js +12 -13
- package/dist/components/builder/canvas-chrome.js.map +1 -1
- package/dist/components/builder/canvas-dnd.d.ts +8 -0
- package/dist/components/builder/canvas-dnd.js +5 -3
- package/dist/components/builder/canvas-dnd.js.map +1 -1
- package/dist/components/builder/canvas-render-context.d.ts +23 -24
- package/dist/components/builder/canvas-render-context.js +35 -60
- package/dist/components/builder/canvas-render-context.js.map +1 -1
- package/dist/components/builder/canvas.js +5 -11
- package/dist/components/builder/canvas.js.map +1 -1
- package/dist/components/builder/clipboard-actions.d.ts +14 -6
- package/dist/components/builder/clipboard-actions.js +29 -20
- package/dist/components/builder/clipboard-actions.js.map +1 -1
- package/dist/components/builder/composition-field-editor.d.ts +19 -26
- package/dist/components/builder/composition-field-editor.js +30 -52
- package/dist/components/builder/composition-field-editor.js.map +1 -1
- package/dist/components/builder/insert-context.d.ts +3 -1
- package/dist/components/builder/insert-context.js +15 -10
- package/dist/components/builder/insert-context.js.map +1 -1
- package/dist/components/builder/page-tree.d.ts +29 -0
- package/dist/components/builder/page-tree.js +56 -58
- package/dist/components/builder/page-tree.js.map +1 -1
- package/dist/components/builder/reuse.js +5 -2
- package/dist/components/builder/reuse.js.map +1 -1
- package/dist/components/cms/asset-field.d.ts +2 -1
- package/dist/components/cms/asset-field.js +39 -83
- package/dist/components/cms/asset-field.js.map +1 -1
- package/dist/components/cms/asset-picker-core.d.ts +77 -40
- package/dist/components/cms/asset-picker-core.js +73 -46
- package/dist/components/cms/asset-picker-core.js.map +1 -1
- package/dist/components/cms/asset-picker.d.ts +48 -19
- package/dist/components/cms/asset-picker.js +69 -30
- package/dist/components/cms/asset-picker.js.map +1 -1
- package/dist/components/cms/cell-edit.d.ts +3 -1
- package/dist/components/cms/cell-edit.js +2 -2
- package/dist/components/cms/cell-edit.js.map +1 -1
- package/dist/components/cms/cms-composition-validators.js +11 -2
- package/dist/components/cms/cms-composition-validators.js.map +1 -1
- package/dist/components/cms/cms-store.js +15 -23
- package/dist/components/cms/cms-store.js.map +1 -1
- package/dist/components/cms/editable-cell.js +3 -3
- package/dist/components/cms/editable-cell.js.map +1 -1
- package/dist/components/cms/gate-validation.d.ts +27 -2
- package/dist/components/cms/gate-validation.js +19 -1
- package/dist/components/cms/gate-validation.js.map +1 -1
- package/dist/components/cms/rich-text/editor.js +2 -2
- package/dist/components/cms/rich-text/editor.js.map +1 -1
- package/dist/components/cms/rich-text/insert.d.ts +7 -0
- package/dist/components/cms/rich-text/insert.js +7 -0
- package/dist/components/cms/rich-text/insert.js.map +1 -1
- package/dist/components/cms/use-document-versions.js +1 -0
- package/dist/components/cms/use-document-versions.js.map +1 -1
- package/dist/components/cms/versions-model.d.ts +0 -12
- package/dist/components/cms/versions-model.js +3 -15
- package/dist/components/cms/versions-model.js.map +1 -1
- package/dist/components/components/component-detail.js +1 -1
- package/dist/components/components/component-detail.js.map +1 -1
- package/dist/components/components/transfer-dialog.js +2 -4
- package/dist/components/components/transfer-dialog.js.map +1 -1
- package/dist/components/components/transfer.d.ts +13 -10
- package/dist/components/components/transfer.js +21 -19
- package/dist/components/components/transfer.js.map +1 -1
- package/dist/components/nav-issues.d.ts +23 -3
- package/dist/components/nav-issues.js +38 -2
- package/dist/components/nav-issues.js.map +1 -1
- package/dist/components/project-session.d.ts +9 -0
- package/dist/components/project-session.js +1 -1
- package/dist/components/project-session.js.map +1 -1
- package/dist/components/redirects/redirects-collection-view-source.d.ts +17 -2
- package/dist/components/redirects/redirects-collection-view-source.js +20 -6
- package/dist/components/redirects/redirects-collection-view-source.js.map +1 -1
- package/dist/components/section-nav.js +26 -2
- package/dist/components/section-nav.js.map +1 -1
- package/dist/components/section-surface.js +3 -2
- package/dist/components/section-surface.js.map +1 -1
- package/dist/components/settings-form.js +1 -2
- package/dist/components/settings-form.js.map +1 -1
- package/dist/components/studio-context.js +1 -18
- package/dist/components/studio-context.js.map +1 -1
- package/dist/components/studio-project-screen.js +3 -5
- package/dist/components/studio-project-screen.js.map +1 -1
- package/dist/lib/ai/apply-content.js +73 -29
- package/dist/lib/ai/apply-content.js.map +1 -1
- package/dist/lib/ai/constraints.d.ts +35 -0
- package/dist/lib/ai/constraints.js +104 -4
- package/dist/lib/ai/constraints.js.map +1 -1
- package/dist/lib/ai/route.js +4 -1
- package/dist/lib/ai/route.js.map +1 -1
- package/dist/lib/ai/shape.d.ts +3 -2
- package/dist/lib/ai/shape.js +16 -24
- package/dist/lib/ai/shape.js.map +1 -1
- package/dist/lib/ai/tools.d.ts +10 -4
- package/dist/lib/ai/tools.js +12 -4
- package/dist/lib/ai/tools.js.map +1 -1
- package/dist/lib/ai/types.d.ts +8 -0
- package/dist/lib/ai/types.js.map +1 -1
- package/dist/lib/ai/update-fields.js +33 -12
- package/dist/lib/ai/update-fields.js.map +1 -1
- package/dist/lib/assets/asset-placements.d.ts +35 -0
- package/dist/lib/assets/asset-placements.js +74 -0
- package/dist/lib/assets/asset-placements.js.map +1 -0
- package/dist/lib/assets/backfill-thumbs.d.ts +9 -8
- package/dist/lib/assets/backfill-thumbs.js +13 -25
- package/dist/lib/assets/backfill-thumbs.js.map +1 -1
- package/dist/lib/clipboard/paste-issues.d.ts +17 -15
- package/dist/lib/clipboard/paste-issues.js +8 -45
- package/dist/lib/clipboard/paste-issues.js.map +1 -1
- package/dist/lib/clipboard/paste-target.d.ts +25 -1
- package/dist/lib/clipboard/paste-target.js +13 -6
- package/dist/lib/clipboard/paste-target.js.map +1 -1
- package/dist/lib/cms/published-documents.js +1 -0
- package/dist/lib/cms/published-documents.js.map +1 -1
- package/dist/lib/cms-views/view-model.d.ts +3 -3
- package/dist/lib/data/collection-content.d.ts +22 -1
- package/dist/lib/data/collection-content.js +15 -0
- package/dist/lib/data/collection-content.js.map +1 -1
- package/dist/lib/data/index.d.ts +0 -3
- package/dist/lib/data/index.js +4 -6
- package/dist/lib/data/index.js.map +1 -1
- package/dist/lib/dnd/drop-rules.d.ts +45 -2
- package/dist/lib/dnd/drop-rules.js +64 -9
- package/dist/lib/dnd/drop-rules.js.map +1 -1
- package/dist/lib/engine-version.d.ts +3 -3
- package/dist/lib/engine-version.js +3 -3
- package/dist/lib/engine-version.js.map +1 -1
- package/dist/lib/i18n/messages.de.js +9 -56
- package/dist/lib/i18n/messages.de.js.map +1 -1
- package/dist/lib/i18n/messages.en.d.ts +8 -53
- package/dist/lib/i18n/messages.en.js +14 -57
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/lib/locale/fallback.js +8 -2
- package/dist/lib/locale/fallback.js.map +1 -1
- package/dist/lib/locale/index.d.ts +0 -1
- package/dist/lib/locale/index.js +0 -1
- package/dist/lib/locale/index.js.map +1 -1
- package/dist/lib/locale/locale-store.d.ts +10 -3
- package/dist/lib/locale/locale-store.js +12 -5
- package/dist/lib/locale/locale-store.js.map +1 -1
- package/dist/lib/operations/session-hydration.d.ts +24 -22
- package/dist/lib/operations/session-hydration.js +15 -53
- package/dist/lib/operations/session-hydration.js.map +1 -1
- package/dist/lib/persistence/convex-adapter.js +63 -75
- package/dist/lib/persistence/convex-adapter.js.map +1 -1
- package/dist/lib/persona/labels.d.ts +0 -2
- package/dist/lib/persona/labels.js +1 -7
- package/dist/lib/persona/labels.js.map +1 -1
- package/dist/lib/registry/index.d.ts +4 -2
- package/dist/lib/registry/index.js +26 -21
- package/dist/lib/registry/index.js.map +1 -1
- package/dist/lib/registry/project-components.js +0 -3
- package/dist/lib/registry/project-components.js.map +1 -1
- package/dist/lib/routes/route-issues.js +4 -5
- package/dist/lib/routes/route-issues.js.map +1 -1
- package/dist/lib/theme.js +3 -2
- package/dist/lib/theme.js.map +1 -1
- package/dist/lib/webhooks/tags.js +11 -14
- package/dist/lib/webhooks/tags.js.map +1 -1
- package/dist/lib/workspace/config-issues.d.ts +6 -0
- package/dist/lib/workspace/config-issues.js +8 -4
- package/dist/lib/workspace/config-issues.js.map +1 -1
- package/dist/lib/workspace/index.d.ts +2 -2
- package/dist/lib/workspace/index.js +2 -2
- package/dist/lib/workspace/index.js.map +1 -1
- package/dist/previews/enumerate.d.ts +27 -9
- package/dist/previews/enumerate.js +27 -6
- package/dist/previews/enumerate.js.map +1 -1
- package/dist/previews/manifest-format.d.ts +9 -0
- package/dist/previews/manifest-format.js.map +1 -1
- package/dist/router/match.d.ts +0 -2
- package/dist/router/match.js +0 -2
- package/dist/router/match.js.map +1 -1
- package/dist/router/navigate-once.d.ts +19 -4
- package/dist/router/navigate-once.js +22 -5
- package/dist/router/navigate-once.js.map +1 -1
- package/dist/ui/badge.d.ts +1 -1
- package/package.json +3 -3
- package/src/styles/studio.css +5 -4
- package/src/styles/tokens.css +72 -55
- package/convex/content.ts +0 -291
- package/dist/components/collab/collab-context.d.ts +0 -27
- package/dist/components/collab/collab-context.js +0 -39
- package/dist/components/collab/collab-context.js.map +0 -1
- package/dist/components/collab/collab-seed.d.ts +0 -15
- package/dist/components/collab/collab-seed.js +0 -20
- package/dist/components/collab/collab-seed.js.map +0 -1
- package/dist/components/extensions/extensions-view.d.ts +0 -7
- package/dist/components/extensions/extensions-view.js +0 -127
- package/dist/components/extensions/extensions-view.js.map +0 -1
- package/dist/lib/bindings/section-bindings.d.ts +0 -24
- package/dist/lib/bindings/section-bindings.js +0 -9
- package/dist/lib/bindings/section-bindings.js.map +0 -1
- package/dist/lib/data/data-store.d.ts +0 -98
- package/dist/lib/data/data-store.js +0 -211
- package/dist/lib/data/data-store.js.map +0 -1
- package/dist/lib/data/providers.d.ts +0 -143
- package/dist/lib/data/providers.js +0 -255
- package/dist/lib/data/providers.js.map +0 -1
- package/dist/lib/data/sample-capture.d.ts +0 -30
- package/dist/lib/data/sample-capture.js +0 -70
- package/dist/lib/data/sample-capture.js.map +0 -1
- package/dist/lib/data/sources-store.d.ts +0 -42
- package/dist/lib/data/sources-store.js +0 -120
- package/dist/lib/data/sources-store.js.map +0 -1
- package/dist/lib/locale/binding-fallback.d.ts +0 -35
- package/dist/lib/locale/binding-fallback.js +0 -73
- package/dist/lib/locale/binding-fallback.js.map +0 -1
- package/dist/lib/starters/kit-session.d.ts +0 -5
- package/dist/lib/starters/kit-session.js +0 -17
- package/dist/lib/starters/kit-session.js.map +0 -1
|
@@ -27,6 +27,12 @@ import type { CustomizationIssue } from '@elytracms/core/studio-core';
|
|
|
27
27
|
* declarations, so it locates the config, not an override list.
|
|
28
28
|
*/
|
|
29
29
|
export declare const PROJECT_CONFIG_POINT = "project";
|
|
30
|
+
/**
|
|
31
|
+
* The file a developer fixes these findings in — interpolated, never localized.
|
|
32
|
+
* Exported for the rail foot's other config-remedy message (EC-606's orphaned
|
|
33
|
+
* collections), so the file name is declared once.
|
|
34
|
+
*/
|
|
35
|
+
export declare const PROJECT_CONFIG_FILE = "elytra.config.ts";
|
|
30
36
|
/**
|
|
31
37
|
* Catalog keys for the issues below (EC-478). Named here, next to the producer, so
|
|
32
38
|
* the studio's i18n parity test asserts coverage against the producer rather than a
|
|
@@ -25,8 +25,12 @@
|
|
|
25
25
|
* declarations, so it locates the config, not an override list.
|
|
26
26
|
*/
|
|
27
27
|
export const PROJECT_CONFIG_POINT = 'project';
|
|
28
|
-
/**
|
|
29
|
-
|
|
28
|
+
/**
|
|
29
|
+
* The file a developer fixes these findings in — interpolated, never localized.
|
|
30
|
+
* Exported for the rail foot's other config-remedy message (EC-606's orphaned
|
|
31
|
+
* collections), so the file name is declared once.
|
|
32
|
+
*/
|
|
33
|
+
export const PROJECT_CONFIG_FILE = 'elytra.config.ts';
|
|
30
34
|
/**
|
|
31
35
|
* Catalog keys for the issues below (EC-478). Named here, next to the producer, so
|
|
32
36
|
* the studio's i18n parity test asserts coverage against the producer rather than a
|
|
@@ -60,10 +64,10 @@ export function projectConfigIssues(collections) {
|
|
|
60
64
|
{
|
|
61
65
|
code: 'missing-configuration',
|
|
62
66
|
severity: 'error',
|
|
63
|
-
message: `This project declares no content collections. Add them to "${
|
|
67
|
+
message: `This project declares no content collections. Add them to "${PROJECT_CONFIG_FILE}" — the studio shows your schema or none, never sample content.`,
|
|
64
68
|
messageRef: {
|
|
65
69
|
key: PROJECT_CONFIG_ISSUE_KEYS.noCollections,
|
|
66
|
-
vars: { file:
|
|
70
|
+
vars: { file: PROJECT_CONFIG_FILE },
|
|
67
71
|
},
|
|
68
72
|
point: PROJECT_CONFIG_POINT,
|
|
69
73
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-issues.js","sourceRoot":"","sources":["../../../src/lib/workspace/config-issues.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,SAAS,CAAA;AAE7C
|
|
1
|
+
{"version":3,"file":"config-issues.js","sourceRoot":"","sources":["../../../src/lib/workspace/config-issues.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,SAAS,CAAA;AAE7C;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,kBAAkB,CAAA;AAErD;;;;GAIG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACvC,aAAa,EAAE,mCAAmC;CAC1C,CAAA;AAEV;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,mBAAmB,CACjC,WAAqC;IAErC,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,CAAA;IACrC,OAAO;QACL;YACE,IAAI,EAAE,uBAAuB;YAC7B,QAAQ,EAAE,OAAO;YACjB,OAAO,EAAE,8DAA8D,mBAAmB,iEAAiE;YAC3J,UAAU,EAAE;gBACV,GAAG,EAAE,yBAAyB,CAAC,aAAa;gBAC5C,IAAI,EAAE,EAAE,IAAI,EAAE,mBAAmB,EAAE;aACpC;YACD,KAAK,EAAE,oBAAoB;SAC5B;KACF,CAAA;AACH,CAAC","sourcesContent":["import type { CollectionDef } from '@elytracms/core/cms-core'\nimport type { CustomizationIssue } from '@elytracms/core/studio-core'\n\n/**\n * EC-479 — project-level configuration findings for the section-rail foot.\n *\n * The engine used to fill a missing declaration with its own `cms-core` fixtures:\n * `?? sampleFilterableCollections` in the CMS store, `?? sampleDocuments` beside\n * it, `?? sampleLocaleConfig` in the locale store. A studio whose config never\n * arrived therefore looked *configured* — it showed six documents about Ada\n * Lovelace under a four-collection schema, in `en/de`, none of which the project\n * had declared. The owner's ruling (2026-07-30): \"fixtures should only show in the\n * playground, but not be hard-wired in the engine\".\n *\n * With the fallbacks retired the same state renders as an empty CMS, which on its\n * own is the OTHER silent failure — the house rule bans both (\"if you are writing\n * a fallback that hides a broken state, that *is* the bug\"). This module is the\n * third option: say so, at the rail foot, where EC-467's reuse issues and EC-469's\n * URL-map issues already report project-level problems, on every studio surface.\n *\n * Pure and deterministic — no config-holder reads, no React. The caller supplies\n * the resolved config (`resolveDeploymentCollections()`), which is also what makes\n * this testable against a synthetic project instead of the live one.\n */\n\n/**\n * The extension-point name stamped on these issues. Not a customization registry\n * like `'sidebar'`/`'fieldRenderers'` — the finding is about the project's own\n * declarations, so it locates the config, not an override list.\n */\nexport const PROJECT_CONFIG_POINT = 'project'\n\n/**\n * The file a developer fixes these findings in — interpolated, never localized.\n * Exported for the rail foot's other config-remedy message (EC-606's orphaned\n * collections), so the file name is declared once.\n */\nexport const PROJECT_CONFIG_FILE = 'elytra.config.ts'\n\n/**\n * Catalog keys for the issues below (EC-478). Named here, next to the producer, so\n * the studio's i18n parity test asserts coverage against the producer rather than a\n * hand-copied list — the same shape as `default-modules.tsx`'s `KEYSPACE_KEYS`.\n */\nexport const PROJECT_CONFIG_ISSUE_KEYS = {\n noCollections: 'customization.issue.noCollections',\n} as const\n\n/**\n * Report what the project has NOT declared but the studio cannot work without.\n *\n * Today that is exactly one thing: collections. A studio with none has no content\n * model at all — the CMS rail expands to nothing, every list is empty, and before\n * EC-479 the store quietly substituted the engine's sample schema so none of that\n * was visible. The remedy is one line in `elytra.config.ts`, which is why the\n * message names the file.\n *\n * Deliberately NOT reported here: an absent locale config. A `Project` only exists\n * for a config-declared project and `resolveWorkspaceConfig` gives every one of\n * them a `locales` array, so \"no locales\" is unreachable from the rail's vantage\n * point; the CMS session guards it directly instead (`CmsProvider`), where it is\n * both reachable and actionable. Reporting an unreachable state would be copy\n * nobody can trigger. Since EC-490 (owner-ratified 2026-08-01) that unreachability\n * is STRUCTURAL rather than incidental: a config declaring no `locales` is rejected\n * outright, so there is no resolved project behind which the question could hide.\n */\nexport function projectConfigIssues(\n collections: readonly CollectionDef[],\n): CustomizationIssue[] {\n if (collections.length > 0) return []\n return [\n {\n code: 'missing-configuration',\n severity: 'error',\n message: `This project declares no content collections. Add them to \"${PROJECT_CONFIG_FILE}\" — the studio shows your schema or none, never sample content.`,\n messageRef: {\n key: PROJECT_CONFIG_ISSUE_KEYS.noCollections,\n vars: { file: PROJECT_CONFIG_FILE },\n },\n point: PROJECT_CONFIG_POINT,\n },\n ]\n}\n"]}
|
|
@@ -4,7 +4,7 @@ import type { SidebarNode } from '../../components/app-views/module.js';
|
|
|
4
4
|
import type { CustomFieldRenderer, StudioConfig } from '../../components/app-views/studio-config.js';
|
|
5
5
|
import type { GeneratedPreviewLookup } from '../../components/builder/blocks-model.js';
|
|
6
6
|
import type { WorkspaceState } from './workspace-source.js';
|
|
7
|
-
export { PROJECT_CONFIG_ISSUE_KEYS, PROJECT_CONFIG_POINT, projectConfigIssues, } from './config-issues.js';
|
|
7
|
+
export { PROJECT_CONFIG_FILE, PROJECT_CONFIG_ISSUE_KEYS, PROJECT_CONFIG_POINT, projectConfigIssues, } from './config-issues.js';
|
|
8
8
|
export { resolveWorkspaceState } from './workspace-source.js';
|
|
9
9
|
export type { WorkspaceSources, WorkspaceState } from './workspace-source.js';
|
|
10
10
|
export { isSingleProjectWorkspace, launcherAutoForwardTarget, persistedProjects, selectSwitcherProjects, soleWorkspaceProject, unprovisionedProjects, } from './switcher-source.js';
|
|
@@ -143,7 +143,7 @@ export declare function resolveDeploymentSidebar(): SidebarNode[] | undefined;
|
|
|
143
143
|
* raw CSS for the preview iframe, so the canvas wears the SITE's chrome. Studio-only
|
|
144
144
|
* (from `defineStudio`, never the delivery bundle); `undefined` when none — the
|
|
145
145
|
* canvas keeps its built-in defaults (`bg-background text-foreground font-sans`
|
|
146
|
-
* body
|
|
146
|
+
* body). Studio-wide, like the rail and field renderers.
|
|
147
147
|
*/
|
|
148
148
|
export declare function resolveDeploymentCanvasTheme(): StudioConfig['canvas'];
|
|
149
149
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { frontendUrlForEnvironment } from '@elytracms/core/studio-core';
|
|
2
2
|
import { provideStudioEnv } from '../persistence/backend-mode.js';
|
|
3
3
|
import { resolveWorkspaceState } from './workspace-source.js';
|
|
4
|
-
export { PROJECT_CONFIG_ISSUE_KEYS, PROJECT_CONFIG_POINT, projectConfigIssues, } from './config-issues.js';
|
|
4
|
+
export { PROJECT_CONFIG_FILE, PROJECT_CONFIG_ISSUE_KEYS, PROJECT_CONFIG_POINT, projectConfigIssues, } from './config-issues.js';
|
|
5
5
|
export { resolveWorkspaceState } from './workspace-source.js';
|
|
6
6
|
export { isSingleProjectWorkspace, launcherAutoForwardTarget, persistedProjects, selectSwitcherProjects, soleWorkspaceProject, unprovisionedProjects, } from './switcher-source.js';
|
|
7
7
|
let cached = null;
|
|
@@ -213,7 +213,7 @@ export function resolveDeploymentSidebar() {
|
|
|
213
213
|
* raw CSS for the preview iframe, so the canvas wears the SITE's chrome. Studio-only
|
|
214
214
|
* (from `defineStudio`, never the delivery bundle); `undefined` when none — the
|
|
215
215
|
* canvas keeps its built-in defaults (`bg-background text-foreground font-sans`
|
|
216
|
-
* body
|
|
216
|
+
* body). Studio-wide, like the rail and field renderers.
|
|
217
217
|
*/
|
|
218
218
|
export function resolveDeploymentCanvasTheme() {
|
|
219
219
|
return studioConfig?.canvas;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/workspace/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { frontendUrlForEnvironment } from '@elytracms/core/studio-core'\nimport type {\n BreakpointConfig,\n ElytraConfigInput,\n ResolvedWorkspaceProject,\n} from '@elytracms/core/studio-core'\nimport type { CollectionDef, LocaleConfig, RouteRecord } from '@elytracms/core/cms-core'\nimport type { SidebarNode } from '../../components/app-views/module'\nimport type { CustomFieldRenderer, StudioConfig } from '../../components/app-views/studio-config'\nimport type { GeneratedPreviewLookup } from '../../components/builder/blocks-model'\nimport { provideStudioEnv } from '../persistence/backend-mode'\nimport { resolveWorkspaceState } from './workspace-source'\nimport type { WorkspaceState } from './workspace-source'\n\nexport {\n PROJECT_CONFIG_ISSUE_KEYS,\n PROJECT_CONFIG_POINT,\n projectConfigIssues,\n} from './config-issues'\nexport { resolveWorkspaceState } from './workspace-source'\nexport type { WorkspaceSources, WorkspaceState } from './workspace-source'\nexport {\n isSingleProjectWorkspace,\n launcherAutoForwardTarget,\n persistedProjects,\n selectSwitcherProjects,\n soleWorkspaceProject,\n unprovisionedProjects,\n} from './switcher-source'\nexport type { SwitcherEntry, SwitcherSourceInput } from './switcher-source'\n\nlet cached: WorkspaceState | null = null\nlet studioConfig: StudioConfig | null = null\n\n/**\n * Mount the studio against a project config (EC-223 mount contract) and an\n * optional studio-only customization config (EC-236). The consumer's entry point\n * does `import config from './elytra.config'` + `import studio from\n * './studio.config'` and calls this — an explicit init, NOT an `import.meta.glob`\n * autoload that reaches into the consumer's filesystem (Oli, 2026-06-19: \"there's\n * always an entry point that can trigger a function call\").\n *\n * Two configs, deliberately: `config` (`elytra.config.ts`) is the project\n * definition, shared with the `frontend/` delivery bundle and projected to data;\n * `studio` (`studio.config.tsx` via `defineStudio`) is live builder UI code — the\n * section rail, custom field renderers, branding — that must NEVER reach delivery.\n * Keeping them separate is the customization boundary principle in the mount\n * signature. Seeds the workspace state + the studio-config holder every later\n * query (`getActiveProjectSlug`, `resolveDeployment*`) reads.\n *\n * EC-357 — `options.env`: the consumer's `import.meta.env`, captured HERE because\n * the entry module is code Vite always transforms live. The engine's own\n * `import.meta.env` reads are unreliable inside dep-optimized package code (Vite ≥8\n * omits custom `VITE_*` values there; Vite 7 froze them at optimize time), which\n * silently broke backend URL / mode / dev-login resolution in real consumers.\n * Always pass it: `createStudio(config, studioConfig, { env: import.meta.env })`.\n */\nexport interface StudioMountOptions {\n /** The consumer entry's `import.meta.env` (see `provideStudioEnv` for the contract). */\n env?: Record<string, unknown>\n}\n\nexport function createStudio(\n config: ElytraConfigInput,\n studio?: StudioConfig,\n options?: StudioMountOptions,\n): void {\n provideStudioEnv(options?.env)\n cached = resolveWorkspaceState({\n fileModule: { default: config },\n fileName: 'elytra.config.ts',\n })\n studioConfig = studio ?? null\n}\n\n/** Test seam: set/clear the studio customization config. Never used by app code. */\nexport function setStudioConfigForTests(next: StudioConfig | null): void {\n studioConfig = next\n}\n\n/**\n * The active project slug (EC-179, vision AD-8). One project per backend, so the\n * active project selects which Convex deployment the studio talks to. The root\n * layout sets this from the URL (`/<slug>`) — or, on the sign-in screen, from\n * the `redirect` target's slug — before the auth/persistence adapters are\n * selected, so the *initial* sign-in authenticates against the project the user\n * is heading for (Sanity-style: authenticate to one workspace, then the full\n * config-based switcher is available). `null` at the launcher → the default\n * (first) configured project's deployment.\n */\nlet activeProjectSlug: string | null = null\n\n/** Set the active project slug; returns whether the resolved deployment changed. */\nexport function setActiveProjectSlug(slug: string | null): boolean {\n const before = resolveDeploymentUrl('')\n activeProjectSlug = slug && slug.trim() !== '' ? slug : null\n return resolveDeploymentUrl('') !== before\n}\n\nexport function getActiveProjectSlug(): string | null {\n return activeProjectSlug\n}\n\n/** Test seam: reset the active project slug. Never used by app code. */\nexport function setActiveProjectSlugForTests(slug: string | null): void {\n activeProjectSlug = slug\n}\n\n/**\n * Reserved first-path segments that are NOT project slugs: the studio's own\n * root routes (EC-220). Project surfaces now live at `/<slug>/...` (no `/p/`\n * prefix), so the studio's root namespace is shared between project slugs and\n * these fixed routes — they must be excluded from slug extraction. Keep in\n * sync with the non-`$projectId` files in `src/routes/`; the launcher (`/`) is\n * the empty segment, excluded by the regex requiring one path char.\n */\nconst RESERVED_ROOT_SEGMENTS: ReadonlySet<string> = new Set(['sign-in', 'styleguide'])\n\n/** Extract a project slug from a studio path (`/<slug>/...`), else `null`. */\nexport function projectSlugFromPath(pathname: string): string | null {\n const match = /^\\/([^/?#]+)/.exec(pathname)\n if (!match) return null\n const segment = decodeURIComponent(match[1]!)\n return RESERVED_ROOT_SEGMENTS.has(segment) ? null : segment\n}\n\n/**\n * The project whose backend the studio should bind for a given location\n * (EC-179). A project path (`/<slug>`) binds that project; the sign-in screen\n * binds the project from its `redirect` target so the *initial* sign-in\n * authenticates against the backend the user is heading for (Sanity-style).\n * Anywhere else (the launcher) → `null`, i.e. the default/first project.\n */\nexport function activeSlugForLocation(pathname: string, redirect?: string): string | null {\n const direct = projectSlugFromPath(pathname)\n if (direct) return direct\n if (pathname === '/sign-in' && redirect) return projectSlugFromPath(redirect)\n return null\n}\n\n/** The active config project (by slug), or the first configured project. */\nfunction activeConfigProject() {\n const state = getWorkspaceState()\n if (state.kind !== 'config') return undefined\n if (activeProjectSlug) {\n const match = state.projects.find((p) => p.slug === activeProjectSlug)\n if (match) return match\n }\n return state.projects[0]\n}\n\n/**\n * The studio's resolved workspace state. Deterministic per boot: the config is\n * static input (bundled file or deployment env), so it is resolved once and\n * cached — every consumer (gate, switcher, launcher) sees the same state.\n */\nexport function getWorkspaceState(): WorkspaceState {\n // Seeded by `createStudio(config)` at the entry point. Until then — or when the\n // consumer wires no config — resolve to the empty `none` state (the single-project\n // golden path), rather than reaching for the filesystem.\n if (!cached) cached = resolveWorkspaceState({})\n return cached\n}\n\n/** Test seam: clear/replace the cached state. Never used by app code. */\nexport function setWorkspaceStateForTests(next: WorkspaceState | null): void {\n cached = next\n}\n\n/**\n * Resolve the Convex deployment URL the studio should connect to (vision AD-8:\n * the backend is wired in config). One project per backend, so the **active**\n * project (see {@link setActiveProjectSlug}) selects the deployment via its\n * `convex.deployment` (or its production environment's). At the launcher (no\n * active project) the first configured project is the default backend. Absent\n * config — or a project without a wired deployment — falls back to\n * `VITE_CONVEX_URL` (`fallbackUrl`) for local single-deployment development.\n */\nexport function resolveDeploymentUrl(fallbackUrl: string): string {\n return projectDeploymentUrl(activeConfigProject(), fallbackUrl)\n}\n\n/** The deployment URL for a specific project slug (per-backend auth checks). */\nexport function deploymentUrlForSlug(slug: string, fallbackUrl: string): string {\n const state = getWorkspaceState()\n const project = state.kind === 'config' ? state.projects.find((p) => p.slug === slug) : undefined\n return projectDeploymentUrl(project, fallbackUrl)\n}\n\n/** A project's wired deployment: per-environment ref → project ref → env fallback. */\nfunction projectDeploymentUrl(\n project: ResolvedWorkspaceProject | undefined,\n fallbackUrl: string,\n): string {\n if (!project) return fallbackUrl\n const envDeployment = project.environments.find((e) => e.convex?.deployment)?.convex?.deployment\n return envDeployment ?? project.convex?.deployment ?? fallbackUrl\n}\n\n/**\n * The slug of the project bound to the active deployment (EC-179). One project\n * per backend, so the active config project's slug identifies the deployment's\n * one project; the Convex adapter stamps it onto deployment-scoped records.\n * Absent config falls back to `fallbackId` (used only when wired by env alone).\n */\nexport function resolveDeploymentProjectId(fallbackId: string): string {\n return activeConfigProject()?.slug ?? fallbackId\n}\n\n/**\n * The CMS collections of the active config project (AD-11, EC-183). CMS\n * collection schemas are config-owned: the studio reads the model from\n * `elytra.config.ts` (served through the persistence port — the backend has no\n * `cmsSchemas` table). `[]` when the active project declares none, or when the\n * studio is wired by env alone with no config.\n */\nexport function resolveDeploymentCollections(): CollectionDef[] {\n return activeConfigProject()?.collections ?? []\n}\n\n/**\n * The canvas preview breakpoints of the active config project (EC-250).\n * Config-sourced (config-as-code, derived from the site's Tailwind `screens`);\n * `undefined` when the project declares none — the canvas then falls back to\n * Tailwind's default screens (see `resolveCanvasViewports`), never a baked-in triple.\n */\nexport function resolveDeploymentBreakpoints(): BreakpointConfig[] | undefined {\n return activeConfigProject()?.breakpoints\n}\n\n/**\n * The custom field renderers from the studio config (EC-238): control name →\n * component. Studio-only live code (from `defineStudio`, never the project schema\n * or the delivery bundle); `undefined` when none — the document editor then uses\n * its built-in control for every field. Studio-wide (not per-project): the rail and\n * renderers customize the studio, not one backend.\n */\nexport function resolveDeploymentFieldRenderers(): Record<string, CustomFieldRenderer> | undefined {\n return studioConfig?.fieldRenderers\n}\n\n/**\n * The studio-UI localization config from the studio config (EC-241): the default\n * locale + imported translation catalogs. Studio-only live code (from `defineStudio`,\n * never the project schema or the delivery bundle); `undefined` when none — the\n * studio then renders its built-in English chrome. Studio-wide, like the rail and\n * field renderers (localizes the studio, not one backend).\n */\nexport function resolveDeploymentI18n(): StudioConfig['i18n'] {\n return studioConfig?.i18n\n}\n\n/**\n * The AI platform config from the studio config (EC-331): provider, knowledge, and\n * limits. Studio-only live code (from `defineStudio`, never the delivery bundle);\n * `undefined` when no `ai` block is configured — the feature is then fully absent\n * (the AI subsystem never boots, so `@elytracms/studio/lib/ai`'s runtime — and the\n * `ai`/`@ai-sdk/*` SDK behind it — is never imported). Studio-wide, like the rail\n * and field renderers.\n */\nexport function resolveDeploymentAi(): StudioConfig['ai'] {\n return studioConfig?.ai\n}\n\n/**\n * The generated-preview lookup from the studio config (EC-439 AD-2, the seam\n * EC-440 fills): a pure `componentId + fixtureName → URL` function the blocks\n * panel threads into its tile model. Studio-only live code (from `defineStudio`,\n * never the delivery bundle); `undefined` when none — tiles then fall back from\n * a curated `manifest.previewImage` straight to the icon+name placeholder.\n * Studio-wide, like the rail and field renderers.\n */\nexport function resolveDeploymentGeneratedPreviews(): GeneratedPreviewLookup | undefined {\n return studioConfig?.previews?.generated\n}\n\n/**\n * The configurable section rail from the studio config (EC-237): an ordered list of\n * `nav.*` nodes (views / groups / separators). Studio-only live code (carries view\n * objects + icons); `undefined` when none — the studio then derives the default\n * rail from its built-in views. Invalid entries surface as visible issues at\n * resolution, never silent.\n */\nexport function resolveDeploymentSidebar(): SidebarNode[] | undefined {\n return studioConfig?.sidebar\n}\n\n/**\n * The canvas page-surface theme from the studio config (EC-389): body classes +\n * raw CSS for the preview iframe, so the canvas wears the SITE's chrome. Studio-only\n * (from `defineStudio`, never the delivery bundle); `undefined` when none — the\n * canvas keeps its built-in defaults (`bg-background text-foreground font-sans`\n * body, kit `tokensCss` only). Studio-wide, like the rail and field renderers.\n */\nexport function resolveDeploymentCanvasTheme(): StudioConfig['canvas'] {\n return studioConfig?.canvas\n}\n\n/**\n * The reusable-elements declaration from the studio config (EC-467): WHICH\n * component is the project's reuse embed. Studio-only (from `defineStudio`, never\n * the delivery bundle); `undefined` when none — the studio then INFERS the embed\n * from the manifests + collections exactly as it always has (absence means infer,\n * not off). Studio-wide, like the rail and field renderers.\n */\nexport function resolveDeploymentReuse(): StudioConfig['reuse'] {\n return studioConfig?.reuse\n}\n\n/**\n * The resolved URL routes of the active config project (EC-207, \"routes as\n * code\"). Routes are config-sourced: the studio reads the materialized\n * `RouteRecord[]` from the active `elytra.config.ts` project — never from the\n * backend (the `routes` system collection is retired). The Routes surface and\n * the per-page URL preview both read through here. `[]` when the active project\n * declares none, or when the studio is wired by env alone with no config.\n */\nexport function resolveDeploymentRoutes(): RouteRecord[] {\n return activeConfigProject()?.routes ?? []\n}\n\n// Redirects are editorial DATA now (EC-303), managed in the studio Redirects rail\n// and served dynamically — no longer resolved from config. `resolveDeploymentRedirects`\n// is retired; static/infra redirects are the dev's job via native next.config/vercel.\n\n/**\n * The configured locales of the active config project (AD-8). The same locale\n * set the host stamps onto its per-locale cache tags — used by the revalidation\n * webhook layer (EC-192) so a publish always invalidates EVERY configured locale\n * (e.g. `/de`, `/fr`), never just the default. `[]` when wired by env alone.\n */\nexport function resolveDeploymentLocales(): string[] {\n return activeConfigProject()?.locales.locales ?? []\n}\n\n/**\n * The active config project's `LocaleConfig` (EC-357 finding #3). Locales are\n * structure and structure is code: when `elytra.config.ts` declares them, they\n * drive the studio's ambient locale switcher in EVERY mode — the CMS surface must\n * not fall back to the sample `en/de` fixture (playground, or a connected first\n * run with nothing persisted) when the config says otherwise. `undefined` when no\n * config project is active (env-only wiring keeps the existing kit/sample\n * fallbacks).\n *\n * EC-519: this used to REASSEMBLE the config's flat `locales` + `defaultLocale`\n * pair into a `LocaleConfig`, and had grown its own repair rule — an absent or\n * out-of-set default fell back to `locales[0]`. That is a fourth place a locale\n * could be invented, in the surface furthest from the author. The resolver now\n * hands over the same `LocaleConfig` the delivery host takes, so there is nothing\n * to reassemble and nothing to repair: a default outside the set is a config\n * ERROR (`default-locale-not-in-locales`) and the project never resolves.\n */\nexport function resolveDeploymentLocaleConfig(): LocaleConfig | undefined {\n return activeConfigProject()?.locales\n}\n\n/**\n * The active project's components reference (EC-194, AD-11): where the consuming\n * repo's OWN real components live (`{ package }` or `{ dir }`). When set, the\n * studio registry composes with the repo's components instead of the built-in\n * demo fixtures — so the page builder edits the repo's actual component library.\n * `undefined` → the project ships no code components; the studio falls back to the\n * golden-path fixtures.\n */\nexport function resolveDeploymentComponents() {\n return activeConfigProject()?.components\n}\n\n/**\n * The active project's public site URL (EC-215): the live site's base URL the\n * studio links to (a document's live URL + open-live button, EC-216). Resolved\n * in lockstep with {@link resolveDeploymentUrl} — the environment bound to the\n * active deployment (the first with a wired deployment) provides the URL, falling\n * back to the project-level `frontendUrl`. No active-environment selector exists\n * yet, so the bound environment is the active one. `undefined` when the active\n * project (or env-only wiring) configures no frontend URL.\n */\nexport function resolveDeploymentFrontendUrl(): string | undefined {\n const project = activeConfigProject()\n if (!project) return undefined\n const boundEnv = project.environments.find((e) => e.convex?.deployment)\n return frontendUrlForEnvironment(project, boundEnv?.key)\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/workspace/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { frontendUrlForEnvironment } from '@elytracms/core/studio-core'\nimport type {\n BreakpointConfig,\n ElytraConfigInput,\n ResolvedWorkspaceProject,\n} from '@elytracms/core/studio-core'\nimport type { CollectionDef, LocaleConfig, RouteRecord } from '@elytracms/core/cms-core'\nimport type { SidebarNode } from '../../components/app-views/module'\nimport type { CustomFieldRenderer, StudioConfig } from '../../components/app-views/studio-config'\nimport type { GeneratedPreviewLookup } from '../../components/builder/blocks-model'\nimport { provideStudioEnv } from '../persistence/backend-mode'\nimport { resolveWorkspaceState } from './workspace-source'\nimport type { WorkspaceState } from './workspace-source'\n\nexport {\n PROJECT_CONFIG_FILE,\n PROJECT_CONFIG_ISSUE_KEYS,\n PROJECT_CONFIG_POINT,\n projectConfigIssues,\n} from './config-issues'\nexport { resolveWorkspaceState } from './workspace-source'\nexport type { WorkspaceSources, WorkspaceState } from './workspace-source'\nexport {\n isSingleProjectWorkspace,\n launcherAutoForwardTarget,\n persistedProjects,\n selectSwitcherProjects,\n soleWorkspaceProject,\n unprovisionedProjects,\n} from './switcher-source'\nexport type { SwitcherEntry, SwitcherSourceInput } from './switcher-source'\n\nlet cached: WorkspaceState | null = null\nlet studioConfig: StudioConfig | null = null\n\n/**\n * Mount the studio against a project config (EC-223 mount contract) and an\n * optional studio-only customization config (EC-236). The consumer's entry point\n * does `import config from './elytra.config'` + `import studio from\n * './studio.config'` and calls this — an explicit init, NOT an `import.meta.glob`\n * autoload that reaches into the consumer's filesystem (Oli, 2026-06-19: \"there's\n * always an entry point that can trigger a function call\").\n *\n * Two configs, deliberately: `config` (`elytra.config.ts`) is the project\n * definition, shared with the `frontend/` delivery bundle and projected to data;\n * `studio` (`studio.config.tsx` via `defineStudio`) is live builder UI code — the\n * section rail, custom field renderers, branding — that must NEVER reach delivery.\n * Keeping them separate is the customization boundary principle in the mount\n * signature. Seeds the workspace state + the studio-config holder every later\n * query (`getActiveProjectSlug`, `resolveDeployment*`) reads.\n *\n * EC-357 — `options.env`: the consumer's `import.meta.env`, captured HERE because\n * the entry module is code Vite always transforms live. The engine's own\n * `import.meta.env` reads are unreliable inside dep-optimized package code (Vite ≥8\n * omits custom `VITE_*` values there; Vite 7 froze them at optimize time), which\n * silently broke backend URL / mode / dev-login resolution in real consumers.\n * Always pass it: `createStudio(config, studioConfig, { env: import.meta.env })`.\n */\nexport interface StudioMountOptions {\n /** The consumer entry's `import.meta.env` (see `provideStudioEnv` for the contract). */\n env?: Record<string, unknown>\n}\n\nexport function createStudio(\n config: ElytraConfigInput,\n studio?: StudioConfig,\n options?: StudioMountOptions,\n): void {\n provideStudioEnv(options?.env)\n cached = resolveWorkspaceState({\n fileModule: { default: config },\n fileName: 'elytra.config.ts',\n })\n studioConfig = studio ?? null\n}\n\n/** Test seam: set/clear the studio customization config. Never used by app code. */\nexport function setStudioConfigForTests(next: StudioConfig | null): void {\n studioConfig = next\n}\n\n/**\n * The active project slug (EC-179, vision AD-8). One project per backend, so the\n * active project selects which Convex deployment the studio talks to. The root\n * layout sets this from the URL (`/<slug>`) — or, on the sign-in screen, from\n * the `redirect` target's slug — before the auth/persistence adapters are\n * selected, so the *initial* sign-in authenticates against the project the user\n * is heading for (Sanity-style: authenticate to one workspace, then the full\n * config-based switcher is available). `null` at the launcher → the default\n * (first) configured project's deployment.\n */\nlet activeProjectSlug: string | null = null\n\n/** Set the active project slug; returns whether the resolved deployment changed. */\nexport function setActiveProjectSlug(slug: string | null): boolean {\n const before = resolveDeploymentUrl('')\n activeProjectSlug = slug && slug.trim() !== '' ? slug : null\n return resolveDeploymentUrl('') !== before\n}\n\nexport function getActiveProjectSlug(): string | null {\n return activeProjectSlug\n}\n\n/** Test seam: reset the active project slug. Never used by app code. */\nexport function setActiveProjectSlugForTests(slug: string | null): void {\n activeProjectSlug = slug\n}\n\n/**\n * Reserved first-path segments that are NOT project slugs: the studio's own\n * root routes (EC-220). Project surfaces now live at `/<slug>/...` (no `/p/`\n * prefix), so the studio's root namespace is shared between project slugs and\n * these fixed routes — they must be excluded from slug extraction. Keep in\n * sync with the non-`$projectId` files in `src/routes/`; the launcher (`/`) is\n * the empty segment, excluded by the regex requiring one path char.\n */\nconst RESERVED_ROOT_SEGMENTS: ReadonlySet<string> = new Set(['sign-in', 'styleguide'])\n\n/** Extract a project slug from a studio path (`/<slug>/...`), else `null`. */\nexport function projectSlugFromPath(pathname: string): string | null {\n const match = /^\\/([^/?#]+)/.exec(pathname)\n if (!match) return null\n const segment = decodeURIComponent(match[1]!)\n return RESERVED_ROOT_SEGMENTS.has(segment) ? null : segment\n}\n\n/**\n * The project whose backend the studio should bind for a given location\n * (EC-179). A project path (`/<slug>`) binds that project; the sign-in screen\n * binds the project from its `redirect` target so the *initial* sign-in\n * authenticates against the backend the user is heading for (Sanity-style).\n * Anywhere else (the launcher) → `null`, i.e. the default/first project.\n */\nexport function activeSlugForLocation(pathname: string, redirect?: string): string | null {\n const direct = projectSlugFromPath(pathname)\n if (direct) return direct\n if (pathname === '/sign-in' && redirect) return projectSlugFromPath(redirect)\n return null\n}\n\n/** The active config project (by slug), or the first configured project. */\nfunction activeConfigProject() {\n const state = getWorkspaceState()\n if (state.kind !== 'config') return undefined\n if (activeProjectSlug) {\n const match = state.projects.find((p) => p.slug === activeProjectSlug)\n if (match) return match\n }\n return state.projects[0]\n}\n\n/**\n * The studio's resolved workspace state. Deterministic per boot: the config is\n * static input (bundled file or deployment env), so it is resolved once and\n * cached — every consumer (gate, switcher, launcher) sees the same state.\n */\nexport function getWorkspaceState(): WorkspaceState {\n // Seeded by `createStudio(config)` at the entry point. Until then — or when the\n // consumer wires no config — resolve to the empty `none` state (the single-project\n // golden path), rather than reaching for the filesystem.\n if (!cached) cached = resolveWorkspaceState({})\n return cached\n}\n\n/** Test seam: clear/replace the cached state. Never used by app code. */\nexport function setWorkspaceStateForTests(next: WorkspaceState | null): void {\n cached = next\n}\n\n/**\n * Resolve the Convex deployment URL the studio should connect to (vision AD-8:\n * the backend is wired in config). One project per backend, so the **active**\n * project (see {@link setActiveProjectSlug}) selects the deployment via its\n * `convex.deployment` (or its production environment's). At the launcher (no\n * active project) the first configured project is the default backend. Absent\n * config — or a project without a wired deployment — falls back to\n * `VITE_CONVEX_URL` (`fallbackUrl`) for local single-deployment development.\n */\nexport function resolveDeploymentUrl(fallbackUrl: string): string {\n return projectDeploymentUrl(activeConfigProject(), fallbackUrl)\n}\n\n/** The deployment URL for a specific project slug (per-backend auth checks). */\nexport function deploymentUrlForSlug(slug: string, fallbackUrl: string): string {\n const state = getWorkspaceState()\n const project = state.kind === 'config' ? state.projects.find((p) => p.slug === slug) : undefined\n return projectDeploymentUrl(project, fallbackUrl)\n}\n\n/** A project's wired deployment: per-environment ref → project ref → env fallback. */\nfunction projectDeploymentUrl(\n project: ResolvedWorkspaceProject | undefined,\n fallbackUrl: string,\n): string {\n if (!project) return fallbackUrl\n const envDeployment = project.environments.find((e) => e.convex?.deployment)?.convex?.deployment\n return envDeployment ?? project.convex?.deployment ?? fallbackUrl\n}\n\n/**\n * The slug of the project bound to the active deployment (EC-179). One project\n * per backend, so the active config project's slug identifies the deployment's\n * one project; the Convex adapter stamps it onto deployment-scoped records.\n * Absent config falls back to `fallbackId` (used only when wired by env alone).\n */\nexport function resolveDeploymentProjectId(fallbackId: string): string {\n return activeConfigProject()?.slug ?? fallbackId\n}\n\n/**\n * The CMS collections of the active config project (AD-11, EC-183). CMS\n * collection schemas are config-owned: the studio reads the model from\n * `elytra.config.ts` (served through the persistence port — the backend has no\n * `cmsSchemas` table). `[]` when the active project declares none, or when the\n * studio is wired by env alone with no config.\n */\nexport function resolveDeploymentCollections(): CollectionDef[] {\n return activeConfigProject()?.collections ?? []\n}\n\n/**\n * The canvas preview breakpoints of the active config project (EC-250).\n * Config-sourced (config-as-code, derived from the site's Tailwind `screens`);\n * `undefined` when the project declares none — the canvas then falls back to\n * Tailwind's default screens (see `resolveCanvasViewports`), never a baked-in triple.\n */\nexport function resolveDeploymentBreakpoints(): BreakpointConfig[] | undefined {\n return activeConfigProject()?.breakpoints\n}\n\n/**\n * The custom field renderers from the studio config (EC-238): control name →\n * component. Studio-only live code (from `defineStudio`, never the project schema\n * or the delivery bundle); `undefined` when none — the document editor then uses\n * its built-in control for every field. Studio-wide (not per-project): the rail and\n * renderers customize the studio, not one backend.\n */\nexport function resolveDeploymentFieldRenderers(): Record<string, CustomFieldRenderer> | undefined {\n return studioConfig?.fieldRenderers\n}\n\n/**\n * The studio-UI localization config from the studio config (EC-241): the default\n * locale + imported translation catalogs. Studio-only live code (from `defineStudio`,\n * never the project schema or the delivery bundle); `undefined` when none — the\n * studio then renders its built-in English chrome. Studio-wide, like the rail and\n * field renderers (localizes the studio, not one backend).\n */\nexport function resolveDeploymentI18n(): StudioConfig['i18n'] {\n return studioConfig?.i18n\n}\n\n/**\n * The AI platform config from the studio config (EC-331): provider, knowledge, and\n * limits. Studio-only live code (from `defineStudio`, never the delivery bundle);\n * `undefined` when no `ai` block is configured — the feature is then fully absent\n * (the AI subsystem never boots, so `@elytracms/studio/lib/ai`'s runtime — and the\n * `ai`/`@ai-sdk/*` SDK behind it — is never imported). Studio-wide, like the rail\n * and field renderers.\n */\nexport function resolveDeploymentAi(): StudioConfig['ai'] {\n return studioConfig?.ai\n}\n\n/**\n * The generated-preview lookup from the studio config (EC-439 AD-2, the seam\n * EC-440 fills): a pure `componentId + fixtureName → URL` function the blocks\n * panel threads into its tile model. Studio-only live code (from `defineStudio`,\n * never the delivery bundle); `undefined` when none — tiles then fall back from\n * a curated `manifest.previewImage` straight to the icon+name placeholder.\n * Studio-wide, like the rail and field renderers.\n */\nexport function resolveDeploymentGeneratedPreviews(): GeneratedPreviewLookup | undefined {\n return studioConfig?.previews?.generated\n}\n\n/**\n * The configurable section rail from the studio config (EC-237): an ordered list of\n * `nav.*` nodes (views / groups / separators). Studio-only live code (carries view\n * objects + icons); `undefined` when none — the studio then derives the default\n * rail from its built-in views. Invalid entries surface as visible issues at\n * resolution, never silent.\n */\nexport function resolveDeploymentSidebar(): SidebarNode[] | undefined {\n return studioConfig?.sidebar\n}\n\n/**\n * The canvas page-surface theme from the studio config (EC-389): body classes +\n * raw CSS for the preview iframe, so the canvas wears the SITE's chrome. Studio-only\n * (from `defineStudio`, never the delivery bundle); `undefined` when none — the\n * canvas keeps its built-in defaults (`bg-background text-foreground font-sans`\n * body). Studio-wide, like the rail and field renderers.\n */\nexport function resolveDeploymentCanvasTheme(): StudioConfig['canvas'] {\n return studioConfig?.canvas\n}\n\n/**\n * The reusable-elements declaration from the studio config (EC-467): WHICH\n * component is the project's reuse embed. Studio-only (from `defineStudio`, never\n * the delivery bundle); `undefined` when none — the studio then INFERS the embed\n * from the manifests + collections exactly as it always has (absence means infer,\n * not off). Studio-wide, like the rail and field renderers.\n */\nexport function resolveDeploymentReuse(): StudioConfig['reuse'] {\n return studioConfig?.reuse\n}\n\n/**\n * The resolved URL routes of the active config project (EC-207, \"routes as\n * code\"). Routes are config-sourced: the studio reads the materialized\n * `RouteRecord[]` from the active `elytra.config.ts` project — never from the\n * backend (the `routes` system collection is retired). The Routes surface and\n * the per-page URL preview both read through here. `[]` when the active project\n * declares none, or when the studio is wired by env alone with no config.\n */\nexport function resolveDeploymentRoutes(): RouteRecord[] {\n return activeConfigProject()?.routes ?? []\n}\n\n// Redirects are editorial DATA now (EC-303), managed in the studio Redirects rail\n// and served dynamically — no longer resolved from config. `resolveDeploymentRedirects`\n// is retired; static/infra redirects are the dev's job via native next.config/vercel.\n\n/**\n * The configured locales of the active config project (AD-8). The same locale\n * set the host stamps onto its per-locale cache tags — used by the revalidation\n * webhook layer (EC-192) so a publish always invalidates EVERY configured locale\n * (e.g. `/de`, `/fr`), never just the default. `[]` when wired by env alone.\n */\nexport function resolveDeploymentLocales(): string[] {\n return activeConfigProject()?.locales.locales ?? []\n}\n\n/**\n * The active config project's `LocaleConfig` (EC-357 finding #3). Locales are\n * structure and structure is code: when `elytra.config.ts` declares them, they\n * drive the studio's ambient locale switcher in EVERY mode — the CMS surface must\n * not fall back to the sample `en/de` fixture (playground, or a connected first\n * run with nothing persisted) when the config says otherwise. `undefined` when no\n * config project is active (env-only wiring keeps the existing kit/sample\n * fallbacks).\n *\n * EC-519: this used to REASSEMBLE the config's flat `locales` + `defaultLocale`\n * pair into a `LocaleConfig`, and had grown its own repair rule — an absent or\n * out-of-set default fell back to `locales[0]`. That is a fourth place a locale\n * could be invented, in the surface furthest from the author. The resolver now\n * hands over the same `LocaleConfig` the delivery host takes, so there is nothing\n * to reassemble and nothing to repair: a default outside the set is a config\n * ERROR (`default-locale-not-in-locales`) and the project never resolves.\n */\nexport function resolveDeploymentLocaleConfig(): LocaleConfig | undefined {\n return activeConfigProject()?.locales\n}\n\n/**\n * The active project's components reference (EC-194, AD-11): where the consuming\n * repo's OWN real components live (`{ package }` or `{ dir }`). When set, the\n * studio registry composes with the repo's components instead of the built-in\n * demo fixtures — so the page builder edits the repo's actual component library.\n * `undefined` → the project ships no code components; the studio falls back to the\n * golden-path fixtures.\n */\nexport function resolveDeploymentComponents() {\n return activeConfigProject()?.components\n}\n\n/**\n * The active project's public site URL (EC-215): the live site's base URL the\n * studio links to (a document's live URL + open-live button, EC-216). Resolved\n * in lockstep with {@link resolveDeploymentUrl} — the environment bound to the\n * active deployment (the first with a wired deployment) provides the URL, falling\n * back to the project-level `frontendUrl`. No active-environment selector exists\n * yet, so the bound environment is the active one. `undefined` when the active\n * project (or env-only wiring) configures no frontend URL.\n */\nexport function resolveDeploymentFrontendUrl(): string | undefined {\n const project = activeConfigProject()\n if (!project) return undefined\n const boundEnv = project.environments.find((e) => e.convex?.deployment)\n return frontendUrlForEnvironment(project, boundEnv?.key)\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ComponentFixture, ComponentManifest } from '@elytracms/core/component-registry';
|
|
1
|
+
import type { ComponentFixture, ComponentIssue, ComponentManifest } from '@elytracms/core/component-registry';
|
|
2
2
|
import type { ComponentImplementations } from '@elytracms/core/runtime-renderer';
|
|
3
3
|
import type { StudioRegistry } from '../lib/registry/index.js';
|
|
4
4
|
/**
|
|
@@ -23,13 +23,19 @@ export interface PreviewEntrySpec {
|
|
|
23
23
|
/** The fixture name, or {@link DEFAULT_VARIANT} for a fixture-less manifest. */
|
|
24
24
|
fixture: string;
|
|
25
25
|
/**
|
|
26
|
-
* The render
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* props/slotChildren (`fixture: null` for the implicit Default variant).
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
26
|
+
* The DECLARED render inputs the CLI hashes per entry for staleness: the
|
|
27
|
+
* manifest's prop + slot shape (defaults live in the prop field-defs, so
|
|
28
|
+
* seeded renders are covered) and the declared fixture's own
|
|
29
|
+
* props/slotChildren (`fixture: null` for the implicit Default variant). A
|
|
30
|
+
* change here re-renders one entry.
|
|
31
|
+
*
|
|
32
|
+
* NOT in here: the component implementation and the theme — a config arrives
|
|
33
|
+
* evaluated, so neither has a per-entry identity. The CLI covers them as
|
|
34
|
+
* manifest-level render dependencies instead (EC-600: content hashes of the
|
|
35
|
+
* project source files the config imports plus the theme `@import` chain),
|
|
36
|
+
* and a change there re-renders every entry. Before EC-600 this comment said
|
|
37
|
+
* regeneration caught such drift; it did not — regeneration only rendered what
|
|
38
|
+
* this hash called stale.
|
|
33
39
|
*/
|
|
34
40
|
hashInput: {
|
|
35
41
|
manifest: {
|
|
@@ -50,12 +56,24 @@ export interface PreviewEntrySpec {
|
|
|
50
56
|
* id). A config that fails shape-resolution contributes no project components
|
|
51
57
|
* — the primitives still enumerate, and the CLI surfaces config problems
|
|
52
58
|
* through the studio/doctor, not here.
|
|
59
|
+
*
|
|
60
|
+
* EC-555: the kept entries' implementations (`unknown` in the config) go through
|
|
61
|
+
* core's one registration step, like the studio registry's and the Next host's —
|
|
62
|
+
* a non-component is withheld (its preview renders the missing-component
|
|
63
|
+
* fallback, never `createElement`) and returned as an `invalid-implementation`
|
|
64
|
+
* issue. The first-wins dedupe runs BEFORE the check, so a withheld entry still
|
|
65
|
+
* claims its id.
|
|
53
66
|
*/
|
|
54
67
|
export declare function previewComponentsFromConfig(config: unknown): {
|
|
55
68
|
manifests: ComponentManifest[];
|
|
56
69
|
implementations: ComponentImplementations;
|
|
70
|
+
issues: ComponentIssue[];
|
|
57
71
|
};
|
|
58
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* The {@link StudioRegistry}-shaped registry the harness renders against. Its
|
|
74
|
+
* `registry.issues` carries the implementation issues too — the same list the
|
|
75
|
+
* studio registry records them into (EC-555).
|
|
76
|
+
*/
|
|
59
77
|
export declare function previewRegistryFromConfig(config: unknown): StudioRegistry;
|
|
60
78
|
/**
|
|
61
79
|
* Enumerate every generated-preview entry for a config: canvas-eligible
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ComponentRegistry, isCanvasEligible } from '@elytracms/core/component-registry';
|
|
2
|
-
import { basePrimitives, getImplementation } from '@elytracms/core/runtime-renderer';
|
|
2
|
+
import { basePrimitives, getImplementation, registerImplementations } from '@elytracms/core/runtime-renderer';
|
|
3
3
|
import { resolveWorkspaceConfig } from '@elytracms/core/studio-core';
|
|
4
4
|
import { DEFAULT_VARIANT } from '../components/builder/blocks-model.js';
|
|
5
5
|
/**
|
|
@@ -10,6 +10,13 @@ import { DEFAULT_VARIANT } from '../components/builder/blocks-model.js';
|
|
|
10
10
|
* id). A config that fails shape-resolution contributes no project components
|
|
11
11
|
* — the primitives still enumerate, and the CLI surfaces config problems
|
|
12
12
|
* through the studio/doctor, not here.
|
|
13
|
+
*
|
|
14
|
+
* EC-555: the kept entries' implementations (`unknown` in the config) go through
|
|
15
|
+
* core's one registration step, like the studio registry's and the Next host's —
|
|
16
|
+
* a non-component is withheld (its preview renders the missing-component
|
|
17
|
+
* fallback, never `createElement`) and returned as an `invalid-implementation`
|
|
18
|
+
* issue. The first-wins dedupe runs BEFORE the check, so a withheld entry still
|
|
19
|
+
* claims its id.
|
|
13
20
|
*/
|
|
14
21
|
export function previewComponentsFromConfig(config) {
|
|
15
22
|
const base = basePrimitives();
|
|
@@ -21,6 +28,7 @@ export function previewComponentsFromConfig(config) {
|
|
|
21
28
|
implementations[manifest.id] = implementation;
|
|
22
29
|
}
|
|
23
30
|
const seen = new Set(manifests.map((m) => m.id));
|
|
31
|
+
const kept = [];
|
|
24
32
|
const resolved = resolveWorkspaceConfig(config);
|
|
25
33
|
for (const project of resolved.projects) {
|
|
26
34
|
for (const entry of project.components ?? []) {
|
|
@@ -28,15 +36,28 @@ export function previewComponentsFromConfig(config) {
|
|
|
28
36
|
continue;
|
|
29
37
|
seen.add(entry.manifest.id);
|
|
30
38
|
manifests.push(entry.manifest);
|
|
31
|
-
|
|
39
|
+
kept.push(entry);
|
|
32
40
|
}
|
|
33
41
|
}
|
|
34
|
-
|
|
42
|
+
// Ids are unique by construction here (deduped against the primitives too),
|
|
43
|
+
// so the merge order below decides nothing.
|
|
44
|
+
const project = registerImplementations(kept);
|
|
45
|
+
return {
|
|
46
|
+
manifests,
|
|
47
|
+
implementations: { ...implementations, ...project.implementations },
|
|
48
|
+
issues: project.issues,
|
|
49
|
+
};
|
|
35
50
|
}
|
|
36
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* The {@link StudioRegistry}-shaped registry the harness renders against. Its
|
|
53
|
+
* `registry.issues` carries the implementation issues too — the same list the
|
|
54
|
+
* studio registry records them into (EC-555).
|
|
55
|
+
*/
|
|
37
56
|
export function previewRegistryFromConfig(config) {
|
|
38
|
-
const { manifests, implementations } = previewComponentsFromConfig(config);
|
|
39
|
-
|
|
57
|
+
const { manifests, implementations, issues } = previewComponentsFromConfig(config);
|
|
58
|
+
const registry = new ComponentRegistry(manifests);
|
|
59
|
+
registry.issues.push(...issues);
|
|
60
|
+
return { registry, implementations };
|
|
40
61
|
}
|
|
41
62
|
/**
|
|
42
63
|
* Enumerate every generated-preview entry for a config: canvas-eligible
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"enumerate.js","sourceRoot":"","sources":["../../src/previews/enumerate.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ComponentType } from 'react'\nimport { ComponentRegistry, isCanvasEligible } from '@elytracms/core/component-registry'\nimport type { ComponentFixture, ComponentManifest } from '@elytracms/core/component-registry'\nimport { basePrimitives, getImplementation } from '@elytracms/core/runtime-renderer'\nimport type { ComponentImplementations } from '@elytracms/core/runtime-renderer'\nimport { resolveWorkspaceConfig } from '@elytracms/core/studio-core'\nimport { DEFAULT_VARIANT } from '../components/builder/blocks-model'\nimport type { StudioRegistry } from '../lib/registry'\n\n/**\n * EC-440 — the fixture-preview enumeration behind `elytra previews`. ONE\n * source of truth for \"which previews exist\" shared by the CLI's node-side\n * driver (hashing, staleness `--check`) and the browser render harness\n * ({@link import('./harness').mountPreviewHarness}) — both derive the entry\n * set from the consumer's `elytra.config.ts` default export through here, so\n * generation, staleness checking, and rendering can never disagree.\n *\n * DATA-FREE by construction: this module reads the consumer CONFIG (registry\n * manifests + implementations — code), never a persistence adapter, auth, or\n * content data. The entry set mirrors the blocks panel exactly (EC-439): every\n * canvas-eligible manifest, one entry per declared fixture, and a single\n * implicit {@link DEFAULT_VARIANT} entry for fixture-less manifests. Base\n * primitives are included — they are insertable tiles too. Layout-helper\n * presets are NOT manifests and keep their placeholder treatment.\n */\n\n/** One renderable preview: a component fixture plus its staleness-hash inputs. */\nexport interface PreviewEntrySpec {\n componentId: string\n /** The fixture name, or {@link DEFAULT_VARIANT} for a fixture-less manifest. */\n fixture: string\n /**\n * The render-relevant inputs the CLI hashes (with the engine version) for\n * staleness: the manifest's prop + slot shape (defaults live in the prop\n * field-defs, so seeded renders are covered) and the declared fixture's own\n * props/slotChildren (`fixture: null` for the implicit Default variant).\n * Deliberately NOT hashed: the component implementation and the theme —\n * drift there is caught by regeneration runs, not by the hash (the ratified\n * EC-440 staleness split).\n */\n hashInput: {\n manifest: { props: ComponentManifest['props']; slots: ComponentManifest['slots'] }\n fixture: {\n props: NonNullable<ComponentFixture['props']> | null\n slotChildren: NonNullable<ComponentFixture['slotChildren']> | null\n } | null\n }\n}\n\n/**\n * The component set a config carries, as the harness registry input: the base\n * primitives plus every resolved project's `components` entries, deduped by\n * component id (first declaration wins — the generated-preview seam is keyed\n * by component id alone, so one image serves every project that registers the\n * id). A config that fails shape-resolution contributes no project components\n * — the primitives still enumerate, and the CLI surfaces config problems\n * through the studio/doctor, not here.\n */\nexport function previewComponentsFromConfig(config: unknown): {\n manifests: ComponentManifest[]\n implementations: ComponentImplementations\n} {\n const base = basePrimitives()\n const manifests: ComponentManifest[] = [...base.manifests]\n const implementations: Record<string, ComponentType<Record<string, unknown>>> = {}\n for (const manifest of base.manifests) {\n const implementation = getImplementation(base.implementations, manifest.id)\n if (implementation) implementations[manifest.id] = implementation\n }\n const seen = new Set(manifests.map((m) => m.id))\n\n const resolved = resolveWorkspaceConfig(config)\n for (const project of resolved.projects) {\n for (const entry of project.components ?? []) {\n if (seen.has(entry.manifest.id)) continue\n seen.add(entry.manifest.id)\n manifests.push(entry.manifest)\n implementations[entry.manifest.id] = entry.implementation as ComponentType<Record<string, unknown>>\n }\n }\n return { manifests, implementations }\n}\n\n/** The {@link StudioRegistry}-shaped registry the harness renders against. */\nexport function previewRegistryFromConfig(config: unknown): StudioRegistry {\n const { manifests, implementations } = previewComponentsFromConfig(config)\n return { registry: new ComponentRegistry(manifests), implementations }\n}\n\n/**\n * Enumerate every generated-preview entry for a config: canvas-eligible\n * manifests only (server-only components never render on the client canvas),\n * one entry per declared fixture in declaration order, else one implicit\n * {@link DEFAULT_VARIANT} entry. Components are sorted by id so the output —\n * and everything the CLI derives from it (manifest JSON, file writes) — is\n * deterministic regardless of registration order.\n */\nexport function previewEntriesForConfig(config: unknown): PreviewEntrySpec[] {\n const { manifests } = previewComponentsFromConfig(config)\n const entries: PreviewEntrySpec[] = []\n for (const manifest of [...manifests].sort((a, b) => a.id.localeCompare(b.id))) {\n if (!isCanvasEligible(manifest)) continue\n const manifestShape = { props: manifest.props, slots: manifest.slots }\n if (manifest.fixtures && manifest.fixtures.length > 0) {\n for (const fixture of manifest.fixtures) {\n entries.push({\n componentId: manifest.id,\n fixture: fixture.name,\n hashInput: {\n manifest: manifestShape,\n fixture: { props: fixture.props ?? null, slotChildren: fixture.slotChildren ?? null },\n },\n })\n }\n } else {\n entries.push({\n componentId: manifest.id,\n fixture: DEFAULT_VARIANT,\n hashInput: { manifest: manifestShape, fixture: null },\n })\n }\n }\n return entries\n}\n"]}
|
|
1
|
+
{"version":3,"file":"enumerate.js","sourceRoot":"","sources":["../../src/previews/enumerate.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ComponentType } from 'react'\nimport { ComponentRegistry, isCanvasEligible } from '@elytracms/core/component-registry'\nimport type {\n ComponentFixture,\n ComponentIssue,\n ComponentManifest,\n} from '@elytracms/core/component-registry'\nimport { basePrimitives, getImplementation, registerImplementations } from '@elytracms/core/runtime-renderer'\nimport type { ComponentImplementations } from '@elytracms/core/runtime-renderer'\nimport { resolveWorkspaceConfig } from '@elytracms/core/studio-core'\nimport type { HostComponentEntry } from '@elytracms/core/studio-core'\nimport { DEFAULT_VARIANT } from '../components/builder/blocks-model'\nimport type { StudioRegistry } from '../lib/registry'\n\n/**\n * EC-440 — the fixture-preview enumeration behind `elytra previews`. ONE\n * source of truth for \"which previews exist\" shared by the CLI's node-side\n * driver (hashing, staleness `--check`) and the browser render harness\n * ({@link import('./harness').mountPreviewHarness}) — both derive the entry\n * set from the consumer's `elytra.config.ts` default export through here, so\n * generation, staleness checking, and rendering can never disagree.\n *\n * DATA-FREE by construction: this module reads the consumer CONFIG (registry\n * manifests + implementations — code), never a persistence adapter, auth, or\n * content data. The entry set mirrors the blocks panel exactly (EC-439): every\n * canvas-eligible manifest, one entry per declared fixture, and a single\n * implicit {@link DEFAULT_VARIANT} entry for fixture-less manifests. Base\n * primitives are included — they are insertable tiles too. Layout-helper\n * presets are NOT manifests and keep their placeholder treatment.\n */\n\n/** One renderable preview: a component fixture plus its staleness-hash inputs. */\nexport interface PreviewEntrySpec {\n componentId: string\n /** The fixture name, or {@link DEFAULT_VARIANT} for a fixture-less manifest. */\n fixture: string\n /**\n * The DECLARED render inputs the CLI hashes per entry for staleness: the\n * manifest's prop + slot shape (defaults live in the prop field-defs, so\n * seeded renders are covered) and the declared fixture's own\n * props/slotChildren (`fixture: null` for the implicit Default variant). A\n * change here re-renders one entry.\n *\n * NOT in here: the component implementation and the theme — a config arrives\n * evaluated, so neither has a per-entry identity. The CLI covers them as\n * manifest-level render dependencies instead (EC-600: content hashes of the\n * project source files the config imports plus the theme `@import` chain),\n * and a change there re-renders every entry. Before EC-600 this comment said\n * regeneration caught such drift; it did not — regeneration only rendered what\n * this hash called stale.\n */\n hashInput: {\n manifest: { props: ComponentManifest['props']; slots: ComponentManifest['slots'] }\n fixture: {\n props: NonNullable<ComponentFixture['props']> | null\n slotChildren: NonNullable<ComponentFixture['slotChildren']> | null\n } | null\n }\n}\n\n/**\n * The component set a config carries, as the harness registry input: the base\n * primitives plus every resolved project's `components` entries, deduped by\n * component id (first declaration wins — the generated-preview seam is keyed\n * by component id alone, so one image serves every project that registers the\n * id). A config that fails shape-resolution contributes no project components\n * — the primitives still enumerate, and the CLI surfaces config problems\n * through the studio/doctor, not here.\n *\n * EC-555: the kept entries' implementations (`unknown` in the config) go through\n * core's one registration step, like the studio registry's and the Next host's —\n * a non-component is withheld (its preview renders the missing-component\n * fallback, never `createElement`) and returned as an `invalid-implementation`\n * issue. The first-wins dedupe runs BEFORE the check, so a withheld entry still\n * claims its id.\n */\nexport function previewComponentsFromConfig(config: unknown): {\n manifests: ComponentManifest[]\n implementations: ComponentImplementations\n issues: ComponentIssue[]\n} {\n const base = basePrimitives()\n const manifests: ComponentManifest[] = [...base.manifests]\n const implementations: Record<string, ComponentType<Record<string, unknown>>> = {}\n for (const manifest of base.manifests) {\n const implementation = getImplementation(base.implementations, manifest.id)\n if (implementation) implementations[manifest.id] = implementation\n }\n const seen = new Set(manifests.map((m) => m.id))\n\n const kept: HostComponentEntry[] = []\n const resolved = resolveWorkspaceConfig(config)\n for (const project of resolved.projects) {\n for (const entry of project.components ?? []) {\n if (seen.has(entry.manifest.id)) continue\n seen.add(entry.manifest.id)\n manifests.push(entry.manifest)\n kept.push(entry)\n }\n }\n // Ids are unique by construction here (deduped against the primitives too),\n // so the merge order below decides nothing.\n const project = registerImplementations(kept)\n return {\n manifests,\n implementations: { ...implementations, ...project.implementations },\n issues: project.issues,\n }\n}\n\n/**\n * The {@link StudioRegistry}-shaped registry the harness renders against. Its\n * `registry.issues` carries the implementation issues too — the same list the\n * studio registry records them into (EC-555).\n */\nexport function previewRegistryFromConfig(config: unknown): StudioRegistry {\n const { manifests, implementations, issues } = previewComponentsFromConfig(config)\n const registry = new ComponentRegistry(manifests)\n registry.issues.push(...issues)\n return { registry, implementations }\n}\n\n/**\n * Enumerate every generated-preview entry for a config: canvas-eligible\n * manifests only (server-only components never render on the client canvas),\n * one entry per declared fixture in declaration order, else one implicit\n * {@link DEFAULT_VARIANT} entry. Components are sorted by id so the output —\n * and everything the CLI derives from it (manifest JSON, file writes) — is\n * deterministic regardless of registration order.\n */\nexport function previewEntriesForConfig(config: unknown): PreviewEntrySpec[] {\n const { manifests } = previewComponentsFromConfig(config)\n const entries: PreviewEntrySpec[] = []\n for (const manifest of [...manifests].sort((a, b) => a.id.localeCompare(b.id))) {\n if (!isCanvasEligible(manifest)) continue\n const manifestShape = { props: manifest.props, slots: manifest.slots }\n if (manifest.fixtures && manifest.fixtures.length > 0) {\n for (const fixture of manifest.fixtures) {\n entries.push({\n componentId: manifest.id,\n fixture: fixture.name,\n hashInput: {\n manifest: manifestShape,\n fixture: { props: fixture.props ?? null, slotChildren: fixture.slotChildren ?? null },\n },\n })\n }\n } else {\n entries.push({\n componentId: manifest.id,\n fixture: DEFAULT_VARIANT,\n hashInput: { manifest: manifestShape, fixture: null },\n })\n }\n }\n return entries\n}\n"]}
|
|
@@ -42,6 +42,15 @@ export interface GeneratedPreviewsManifest {
|
|
|
42
42
|
/** The `@elytracms/studio` version the images were rendered with. */
|
|
43
43
|
engine: string;
|
|
44
44
|
entries: GeneratedPreviewsManifestEntry[];
|
|
45
|
+
/**
|
|
46
|
+
* EC-600 (format v3): the render dependencies the images were rendered against —
|
|
47
|
+
* project source files imported by the config plus the theme `@import` chain, each
|
|
48
|
+
* with its content hash. Written and compared by the CLI; the studio ignores it.
|
|
49
|
+
*/
|
|
50
|
+
renderDeps?: Array<{
|
|
51
|
+
path: string;
|
|
52
|
+
hash: string;
|
|
53
|
+
}>;
|
|
45
54
|
}
|
|
46
55
|
/**
|
|
47
56
|
* Adapt a previews manifest (a plain JSON import) into the EC-439
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest-format.js","sourceRoot":"","sources":["../../src/previews/manifest-format.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"manifest-format.js","sourceRoot":"","sources":["../../src/previews/manifest-format.ts"],"names":[],"mappings":"AAwDA,0EAA0E;AAC1E,SAAS,eAAe,CAAC,KAAc;IACrC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAA;IAC7D,MAAM,KAAK,GAAG,KAAgC,CAAA;IAC9C,OAAO,CACL,OAAO,KAAK,CAAC,WAAW,KAAK,QAAQ;QACrC,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ;QACjC,OAAO,KAAK,CAAC,GAAG,KAAK,QAAQ,CAC9B,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,6BAA6B,CAAC,QAAiB;IAC7D,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAA;IACvC,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACtD,MAAM,OAAO,GAAI,QAAkC,CAAC,OAAO,CAAA;QAC3D,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3B,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;gBAC5B,IAAI,eAAe,CAAC,KAAK,CAAC;oBAAE,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAA;YAC/F,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,CAAC,WAAW,EAAE,WAAW,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,CAAA;AACrF,CAAC;AAED,SAAS,SAAS,CAAC,WAAmB,EAAE,OAAe;IACrD,wEAAwE;IACxE,OAAO,GAAG,WAAW,SAAS,OAAO,EAAE,CAAA;AACzC,CAAC","sourcesContent":["import type { GeneratedPreviewLookup } from '../components/builder/blocks-model'\n\n/**\n * The previews-manifest format (EC-440) — the JSON contract between the\n * `elytra previews` CLI (the writer) and a consumer's `studio.config.tsx`\n * (the reader, via {@link generatedPreviewsFromManifest}).\n *\n * The CLI renders every canvas-eligible component's fixtures headlessly\n * against the consumer theme, writes the images to the studio app's\n * `public/elytra-previews/` (served at `/elytra-previews/...`), and writes\n * this manifest next to `studio.config.tsx` (`studio/elytra-previews.json`)\n * so it is importable as a plain JSON module. Everything in it is\n * deterministic — no timestamps — so an unchanged re-run rewrites nothing.\n */\n\n/**\n * The manifest FORMAT VERSION is declared once, in the CLI (`@elytracms/cli`\n * `src/previews.js` `PREVIEWS_MANIFEST_VERSION`) — the CLI is both its writer and\n * its only reader (`elytra previews --check` reports a mismatch as itself and\n * regenerates). EC-514 removed the mirror this module used to carry: nothing in the\n * studio ever read it, and a hand-kept copy is a drift site with no reader to catch\n * the drift. The reader below is version-agnostic by design — it only ever looks at\n * `componentId`/`fixture`/`url`, so a manifest of any version resolves tiles.\n */\n\n/** One generated preview artifact: a component fixture and its rendered image. */\nexport interface GeneratedPreviewsManifestEntry {\n componentId: string\n /** The fixture name, or `'Default'` for a fixture-less component's implicit variant. */\n fixture: string\n /** Root-relative image URL (the file lives in the studio app's `public/`). */\n url: string\n /**\n * Input hash (sha-256 hex over the stable-serialized inputs: manifest prop +\n * slot shape, fixture props/slotChildren — NOT the engine version, EC-502).\n * `elytra previews` skips entries whose hash is unchanged; `elytra previews\n * --check` flags entries whose hash drifted as stale. Deliberately NOT part of\n * the lookup — the studio only reads `componentId`/`fixture`/`url`.\n */\n hash: string\n}\n\n/** The manifest document `elytra previews` writes (`studio/elytra-previews.json`). */\nexport interface GeneratedPreviewsManifest {\n version: number\n /** The `@elytracms/studio` version the images were rendered with. */\n engine: string\n entries: GeneratedPreviewsManifestEntry[]\n /**\n * EC-600 (format v3): the render dependencies the images were rendered against —\n * project source files imported by the config plus the theme `@import` chain, each\n * with its content hash. Written and compared by the CLI; the studio ignores it.\n */\n renderDeps?: Array<{ path: string; hash: string }>\n}\n\n/** Tolerant entry guard — a malformed entry is skipped, never a crash. */\nfunction isManifestEntry(value: unknown): value is GeneratedPreviewsManifestEntry {\n if (typeof value !== 'object' || value === null) return false\n const entry = value as Record<string, unknown>\n return (\n typeof entry.componentId === 'string' &&\n typeof entry.fixture === 'string' &&\n typeof entry.url === 'string'\n )\n}\n\n/**\n * Adapt a previews manifest (a plain JSON import) into the EC-439\n * {@link GeneratedPreviewLookup} seam. The whole consumer wiring is ~2 lines:\n *\n * ```tsx\n * // studio.config.tsx\n * import previewsManifest from './elytra-previews.json'\n * export default defineStudio({\n * previews: { generated: generatedPreviewsFromManifest(previewsManifest) },\n * })\n * ```\n *\n * Tolerant by design (the input is a generated file a human may hand-edit or\n * delete): a malformed document or entry yields lookup misses — the blocks\n * panel then falls back to the icon+name placeholder — never a crash. A\n * fixture-less component's single implicit variant is looked up (and written\n * by the CLI) under the fixture name `'Default'`.\n */\nexport function generatedPreviewsFromManifest(manifest: unknown): GeneratedPreviewLookup {\n const byKey = new Map<string, string>()\n if (typeof manifest === 'object' && manifest !== null) {\n const entries = (manifest as { entries?: unknown }).entries\n if (Array.isArray(entries)) {\n for (const entry of entries) {\n if (isManifestEntry(entry)) byKey.set(lookupKey(entry.componentId, entry.fixture), entry.url)\n }\n }\n }\n return (componentId, fixtureName) => byKey.get(lookupKey(componentId, fixtureName))\n}\n\nfunction lookupKey(componentId: string, fixture: string): string {\n // NUL (\\u0000) can appear in neither a component id nor a fixture name.\n return `${componentId}\\u0000${fixture}`\n}\n"]}
|
package/dist/router/match.d.ts
CHANGED
|
@@ -53,8 +53,6 @@ export declare const studioPath: {
|
|
|
53
53
|
readonly settings: () => string;
|
|
54
54
|
readonly assets: () => string;
|
|
55
55
|
readonly asset: (assetId: string) => string;
|
|
56
|
-
readonly routes: () => string;
|
|
57
|
-
readonly extensions: () => string;
|
|
58
56
|
readonly builder: () => string;
|
|
59
57
|
readonly components: () => string;
|
|
60
58
|
readonly component: (componentId: string) => string;
|
package/dist/router/match.js
CHANGED
|
@@ -61,8 +61,6 @@ export const studioPath = {
|
|
|
61
61
|
// see asset-detail.tsx) drives it (AD-19).
|
|
62
62
|
assets: () => 'assets',
|
|
63
63
|
asset: (assetId) => `assets/${assetId}`,
|
|
64
|
-
routes: () => 'routes',
|
|
65
|
-
extensions: () => 'extensions',
|
|
66
64
|
builder: () => 'builder',
|
|
67
65
|
components: () => 'components',
|
|
68
66
|
component: (componentId) => `components/${componentId}`,
|
package/dist/router/match.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"match.js","sourceRoot":"","sources":["../../src/router/match.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAMH,yFAAyF;AACzF,SAAS,QAAQ,CAAC,KAAa;IAC7B,OAAO,KAAK;SACT,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;SAChC,MAAM,CAAC,OAAO,CAAC,CAAA;AACpB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,MAAM,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACzC,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAA;IACrD,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAA;AACpD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,KAAK,EAAE,GAAG,EAAE,CAAC,EAAE;IACf,QAAQ,EAAE,GAAG,EAAE,CAAC,UAAU;IAC1B,4EAA4E;IAC5E,0EAA0E;IAC1E,2CAA2C;IAC3C,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ;IACtB,KAAK,EAAE,CAAC,OAAe,EAAE,EAAE,CAAC,UAAU,OAAO,EAAE;IAC/C,
|
|
1
|
+
{"version":3,"file":"match.js","sourceRoot":"","sources":["../../src/router/match.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAMH,yFAAyF;AACzF,SAAS,QAAQ,CAAC,KAAa;IAC7B,OAAO,KAAK;SACT,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;SAChC,MAAM,CAAC,OAAO,CAAC,CAAA;AACpB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,MAAM,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACzC,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAA;IACrD,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAA;AACpD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,KAAK,EAAE,GAAG,EAAE,CAAC,EAAE;IACf,QAAQ,EAAE,GAAG,EAAE,CAAC,UAAU;IAC1B,4EAA4E;IAC5E,0EAA0E;IAC1E,2CAA2C;IAC3C,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ;IACtB,KAAK,EAAE,CAAC,OAAe,EAAE,EAAE,CAAC,UAAU,OAAO,EAAE;IAC/C,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS;IACxB,UAAU,EAAE,GAAG,EAAE,CAAC,YAAY;IAC9B,SAAS,EAAE,CAAC,WAAmB,EAAE,EAAE,CAAC,cAAc,WAAW,EAAE;IAC/D,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK;IAChB,aAAa,EAAE,CAAC,YAAoB,EAAE,EAAE,CAAC,OAAO,YAAY,EAAE;IAC9D,WAAW,EAAE,CAAC,YAAoB,EAAE,UAAkB,EAAE,EAAE,CACxD,OAAO,YAAY,IAAI,UAAU,EAAE;IACrC,4EAA4E;IAC5E,kEAAkE;IAClE,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO;IACpB,IAAI,EAAE,CAAC,MAAc,EAAE,EAAE,CAAC,SAAS,MAAM,EAAE;IAC3C,8EAA8E;IAC9E,4CAA4C;IAC5C,SAAS,EAAE,GAAG,EAAE,CAAC,WAAW;IAC5B,QAAQ,EAAE,CAAC,UAAkB,EAAE,EAAE,CAAC,aAAa,UAAU,EAAE;CACnD,CAAA","sourcesContent":["/**\n * Studio route table (EC-223 Stage 6 — catch-all routing). The studio mounts as\n * a SINGLE splat route per project (`/$projectId/$`); this pure matcher turns the\n * splat's trailing pathname into a typed match that the screen resolver renders.\n * Keeping the URL→screen map inside the engine (instead of in per-consumer route\n * files) is what lets the package own its navigation with a near-empty consumer\n * route tree — the goal of Stage 6.\n *\n * Deterministic and dependency-free: every screen state is reproducible from a\n * pathname string (see `match.test.ts`), satisfying the fixtures-reproducibility\n * convention without needing a router instance.\n *\n * EC-239 (app-view modularization): the route table is no longer a hardcoded\n * per-section switch — the studio's surfaces are a config-extendable VIEW registry\n * derived from the section rail, so the matcher is registry-AGNOSTIC. It splits the\n * splat into a view id (the first segment) + that view's trailing segments; the\n * screen resolver looks the id up in the registry and the view's own `parsePath`\n * interprets the segments (CMS: collection/document; components: id; flat: none).\n * Keeping the matcher free of the surface list is what lets a consuming repo add a\n * routable custom view purely from config.\n *\n * /$projectId → { section: 'index' } (redirect to first view)\n * /$projectId/<id> → { section: 'view', viewId: '<id>', segments: [] }\n * /$projectId/<id>/a/b → { section: 'view', viewId: '<id>', segments: ['a','b'] }\n *\n * The view decides whether its segments resolve; an unknown id or an over-deep\n * path becomes a not-found at the resolver, not here.\n */\n\nexport type StudioRouteMatch =\n | { section: 'index' }\n | { section: 'view'; viewId: string; segments: string[] }\n\n/** Split a splat pathname into clean segments (drops empty/leading/trailing slashes). */\nfunction segments(splat: string): string[] {\n return splat\n .split('/')\n .map((segment) => segment.trim())\n .filter(Boolean)\n}\n\n/**\n * Match the trailing pathname of the project splat (`_splat`) into a view id +\n * its segments. `splat` is everything after `/$projectId/` — e.g. `'cms/page/abc'`\n * for `/p1/cms/page/abc` → `{ viewId: 'cms', segments: ['page', 'abc'] }`. An\n * empty splat is the bare project index (which redirects to the first view).\n */\nexport function matchStudioRoute(splat: string): StudioRouteMatch {\n const [viewId, ...rest] = segments(splat)\n if (viewId === undefined) return { section: 'index' }\n return { section: 'view', viewId, segments: rest }\n}\n\n/**\n * The inverse of {@link matchStudioRoute}: build the `_splat` subpath for a\n * screen. Navigation addresses the single project splat as\n * `to={STUDIO_PROJECT_ROUTE} params={{ projectId, _splat: studioPath.X(...) }}`,\n * so the URL-construction lives in exactly one place — the same module that\n * parses it back. `matchStudioRoute(studioPath.cmsDocument(c, d))` round-trips\n * (pinned in `match.test.ts`), which is what keeps the two directions in sync.\n */\nexport const studioPath = {\n index: () => '',\n settings: () => 'settings',\n // The assets rail (EC-319) — a list + a per-asset detail, mirroring the CMS\n // arity so the SAME portable list surface (plus a bespoke detail surface,\n // see asset-detail.tsx) drives it (AD-19).\n assets: () => 'assets',\n asset: (assetId: string) => `assets/${assetId}`,\n builder: () => 'builder',\n components: () => 'components',\n component: (componentId: string) => `components/${componentId}`,\n cms: () => 'cms',\n cmsCollection: (collectionId: string) => `cms/${collectionId}`,\n cmsDocument: (collectionId: string, documentId: string) =>\n `cms/${collectionId}/${documentId}`,\n // The users rail (EC-240) — a list + a per-member detail, mirroring the CMS\n // arity so the SAME portable list/edit surfaces drive it (AD-19).\n users: () => 'users',\n user: (userId: string) => `users/${userId}`,\n // The redirects rail (EC-303) — editorial redirects as data, same list+detail\n // arity over the portable surfaces (AD-19).\n redirects: () => 'redirects',\n redirect: (redirectId: string) => `redirects/${redirectId}`,\n} as const\n"]}
|
|
@@ -23,7 +23,14 @@
|
|
|
23
23
|
* - one fire per intent per committed location — re-renders and store ticks
|
|
24
24
|
* while the target transition is pending are inert;
|
|
25
25
|
* - a NEW committed location re-arms the attempt, so navigating back to a URL
|
|
26
|
-
* that redirects (e.g. the bare project index) redirects again
|
|
26
|
+
* that redirects (e.g. the bare project index) redirects again;
|
|
27
|
+
* - a redirect rendered by a route the committed location no longer matches is
|
|
28
|
+
* inert (EC-611). While a transition is pending the router keeps rendering
|
|
29
|
+
* the PREVIOUS match set, so a route component outlives its own URL: the
|
|
30
|
+
* launcher (`/`) is still mounted at `/sign-in?redirect=/aurora` until the
|
|
31
|
+
* sign-in module has loaded. Left to fire, its auto-forward and the auth
|
|
32
|
+
* gate's sign-in bounce alternate, each commit re-arming the other — the
|
|
33
|
+
* signed-out boot hang on `@tanstack/react-router` ≥ 1.170.41 (NK-050 F3).
|
|
27
34
|
*/
|
|
28
35
|
export interface NavigateOnceIntent {
|
|
29
36
|
/** Target route path, addressed as a plain string (the router-seam contract). */
|
|
@@ -61,15 +68,23 @@ export interface RedirectAttemptState {
|
|
|
61
68
|
/**
|
|
62
69
|
* The pure fire decision (EC-441's loop breaker, node-tested):
|
|
63
70
|
*
|
|
64
|
-
* 1.
|
|
65
|
-
*
|
|
71
|
+
* 1. the rendering route is not matched by the committed location → no fire
|
|
72
|
+
* (a stale render during a pending transition, EC-611);
|
|
73
|
+
* 2. target already committed → no fire;
|
|
74
|
+
* 3. this intent already fired at this committed location → no fire (the
|
|
66
75
|
* per-render re-fire cycle, and any intent ping-pong at one location);
|
|
67
|
-
*
|
|
76
|
+
* 4. otherwise → fire, and record the intent for this location.
|
|
68
77
|
*/
|
|
69
78
|
export declare function nextRedirectAttempt(state: RedirectAttemptState | null, input: {
|
|
70
79
|
intentKey: string;
|
|
71
80
|
targetHref: string;
|
|
72
81
|
committedHref: string;
|
|
82
|
+
/**
|
|
83
|
+
* Whether the route this redirect renders in is part of the committed
|
|
84
|
+
* location's match set. Absent means "not a route-scoped render" (treated
|
|
85
|
+
* as matched) so the decision stays usable from the root layout.
|
|
86
|
+
*/
|
|
87
|
+
routeMatched?: boolean;
|
|
73
88
|
}): {
|
|
74
89
|
fire: boolean;
|
|
75
90
|
state: RedirectAttemptState;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useLayoutEffect, useRef } from 'react';
|
|
2
|
-
import { useLocation, useNavigate, useRouter } from '@tanstack/react-router';
|
|
2
|
+
import { useLocation, useMatch, useNavigate, useRouter } from '@tanstack/react-router';
|
|
3
3
|
/**
|
|
4
4
|
* Pure value identity of a redirect intent. Fresh-but-equal `params`/`search`
|
|
5
5
|
* objects (the upstream failure mode: new object literal every render) map to
|
|
@@ -35,15 +35,19 @@ export function isSameRedirectTarget(targetHref, committedHref) {
|
|
|
35
35
|
/**
|
|
36
36
|
* The pure fire decision (EC-441's loop breaker, node-tested):
|
|
37
37
|
*
|
|
38
|
-
* 1.
|
|
39
|
-
*
|
|
38
|
+
* 1. the rendering route is not matched by the committed location → no fire
|
|
39
|
+
* (a stale render during a pending transition, EC-611);
|
|
40
|
+
* 2. target already committed → no fire;
|
|
41
|
+
* 3. this intent already fired at this committed location → no fire (the
|
|
40
42
|
* per-render re-fire cycle, and any intent ping-pong at one location);
|
|
41
|
-
*
|
|
43
|
+
* 4. otherwise → fire, and record the intent for this location.
|
|
42
44
|
*/
|
|
43
45
|
export function nextRedirectAttempt(state, input) {
|
|
44
46
|
const base = state && state.committedHref === input.committedHref
|
|
45
47
|
? state
|
|
46
48
|
: { committedHref: input.committedHref, firedKeys: new Set() };
|
|
49
|
+
if (input.routeMatched === false)
|
|
50
|
+
return { fire: false, state: base };
|
|
47
51
|
if (isSameRedirectTarget(input.targetHref, input.committedHref))
|
|
48
52
|
return { fire: false, state: base };
|
|
49
53
|
if (base.firedKeys.has(input.intentKey))
|
|
@@ -67,6 +71,12 @@ export function NavigateOnce({ to, params, search, replace }) {
|
|
|
67
71
|
// stable across the pending-transition re-renders that used to re-fire
|
|
68
72
|
// `<Navigate>`, and a commit re-runs the decision exactly once.
|
|
69
73
|
const committedHref = useLocation({ select: (location) => location.href });
|
|
74
|
+
// The route this redirect renders in (EC-611). `strict: false` resolves the
|
|
75
|
+
// NEAREST match, so a redirect in the root layout (the auth gate) reads the
|
|
76
|
+
// root route, which every location matches, and a redirect in a route
|
|
77
|
+
// component reads that route — which the committed location stops matching
|
|
78
|
+
// the moment a transition away from it starts.
|
|
79
|
+
const routeId = useMatch({ strict: false, select: (match) => match.routeId });
|
|
70
80
|
const key = navigateIntentKey({ to, params, search, replace });
|
|
71
81
|
// Latest-value ref: the effect is keyed on the intent's VALUE (`key`), so it
|
|
72
82
|
// reads the current props through a ref instead of listing unstable object
|
|
@@ -81,10 +91,17 @@ export function NavigateOnce({ to, params, search, replace }) {
|
|
|
81
91
|
params: intent.params,
|
|
82
92
|
search: intent.search,
|
|
83
93
|
});
|
|
94
|
+
// Match the COMMITTED location against the route tree (no load, no state
|
|
95
|
+
// change): is the route rendering this redirect still on the URL? During
|
|
96
|
+
// a pending transition the previous match set keeps rendering, so this is
|
|
97
|
+
// the one fact that tells a live redirect from a stale one.
|
|
98
|
+
const routeMatched = routeId === undefined ||
|
|
99
|
+
router.matchRoutes(router.state.location).some((match) => match.routeId === routeId);
|
|
84
100
|
const decision = nextRedirectAttempt(attemptRef.current, {
|
|
85
101
|
intentKey: key,
|
|
86
102
|
targetHref: target.href,
|
|
87
103
|
committedHref,
|
|
104
|
+
routeMatched,
|
|
88
105
|
});
|
|
89
106
|
attemptRef.current = decision.state;
|
|
90
107
|
if (!decision.fire)
|
|
@@ -98,7 +115,7 @@ export function NavigateOnce({ to, params, search, replace }) {
|
|
|
98
115
|
search: intent.search,
|
|
99
116
|
replace: intent.replace,
|
|
100
117
|
});
|
|
101
|
-
}, [key, committedHref, navigate, router]);
|
|
118
|
+
}, [key, committedHref, navigate, router, routeId]);
|
|
102
119
|
return null;
|
|
103
120
|
}
|
|
104
121
|
//# sourceMappingURL=navigate-once.js.map
|