@elytracms/studio 0.0.81 → 0.0.82
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/redirect-revalidate.test.ts +130 -0
- package/convex/revalidate.ts +9 -7
- package/convex/triggers.ts +33 -3
- package/dist/components/ai/ai-chat-panel.js +26 -4
- package/dist/components/ai/ai-chat-panel.js.map +1 -1
- package/dist/components/ai/proposal-card.d.ts +27 -0
- package/dist/components/ai/proposal-card.js +31 -3
- package/dist/components/ai/proposal-card.js.map +1 -1
- package/dist/components/ai/proposal-issues.d.ts +55 -0
- package/dist/components/ai/proposal-issues.js +69 -0
- package/dist/components/ai/proposal-issues.js.map +1 -0
- package/dist/components/ai/translate-document-dialog.js +38 -3
- package/dist/components/ai/translate-document-dialog.js.map +1 -1
- package/dist/components/app-views/studio-config.d.ts +1 -1
- package/dist/components/app-views/studio-config.js +1 -1
- package/dist/components/app-views/studio-config.js.map +1 -1
- package/dist/components/builder/canvas-render-context.js +3 -1
- package/dist/components/builder/canvas-render-context.js.map +1 -1
- package/dist/components/builder/clipboard-actions.js +5 -3
- package/dist/components/builder/clipboard-actions.js.map +1 -1
- package/dist/components/cms/bulk-actions.js +8 -5
- package/dist/components/cms/bulk-actions.js.map +1 -1
- package/dist/components/cms/cell-edit.d.ts +18 -3
- package/dist/components/cms/cell-edit.js +27 -9
- package/dist/components/cms/cell-edit.js.map +1 -1
- package/dist/components/cms/cms-store.d.ts +14 -1
- package/dist/components/cms/cms-store.js +93 -24
- package/dist/components/cms/cms-store.js.map +1 -1
- package/dist/components/cms/collection-table.js +1 -1
- package/dist/components/cms/collection-table.js.map +1 -1
- package/dist/components/cms/detail-editor.js +7 -12
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/document-editor-context.d.ts +12 -1
- package/dist/components/cms/document-editor-context.js.map +1 -1
- package/dist/components/cms/field-control.js +20 -7
- package/dist/components/cms/field-control.js.map +1 -1
- package/dist/components/cms/gate-validation.d.ts +36 -0
- package/dist/components/cms/gate-validation.js +17 -0
- package/dist/components/cms/gate-validation.js.map +1 -0
- package/dist/components/cms/inline-field-errors.d.ts +8 -0
- package/dist/components/cms/inline-field-errors.js +52 -0
- package/dist/components/cms/inline-field-errors.js.map +1 -0
- package/dist/components/cms/issues-pane.js +8 -4
- package/dist/components/cms/issues-pane.js.map +1 -1
- package/dist/components/cms/slug-collision-report.d.ts +41 -0
- package/dist/components/cms/slug-collision-report.js +52 -0
- package/dist/components/cms/slug-collision-report.js.map +1 -0
- package/dist/components/deployment-setup-gate.d.ts +24 -0
- package/dist/components/deployment-setup-gate.js +29 -4
- package/dist/components/deployment-setup-gate.js.map +1 -1
- package/dist/components/issue-severity.d.ts +73 -0
- package/dist/components/issue-severity.js +56 -0
- package/dist/components/issue-severity.js.map +1 -0
- package/dist/components/nav-issues.d.ts +68 -0
- package/dist/components/nav-issues.js +78 -0
- package/dist/components/nav-issues.js.map +1 -0
- package/dist/components/operation-error.js +3 -3
- package/dist/components/operation-error.js.map +1 -1
- package/dist/components/section-nav.js +5 -38
- package/dist/components/section-nav.js.map +1 -1
- package/dist/lib/ai/apply-content.js +6 -7
- package/dist/lib/ai/apply-content.js.map +1 -1
- package/dist/lib/ai/chat-context.d.ts +32 -26
- package/dist/lib/ai/chat-context.js +81 -9
- package/dist/lib/ai/chat-context.js.map +1 -1
- package/dist/lib/ai/constraints.d.ts +42 -0
- package/dist/lib/ai/constraints.js +259 -0
- package/dist/lib/ai/constraints.js.map +1 -0
- package/dist/lib/ai/index.d.ts +2 -0
- package/dist/lib/ai/index.js +2 -0
- package/dist/lib/ai/index.js.map +1 -1
- package/dist/lib/ai/proposal-validation.d.ts +26 -0
- package/dist/lib/ai/proposal-validation.js +17 -0
- package/dist/lib/ai/proposal-validation.js.map +1 -0
- package/dist/lib/ai/shape.js +60 -13
- package/dist/lib/ai/shape.js.map +1 -1
- package/dist/lib/ai/tools.d.ts +8 -4
- package/dist/lib/ai/tools.js +43 -14
- package/dist/lib/ai/tools.js.map +1 -1
- package/dist/lib/ai/translate-document.d.ts +35 -1
- package/dist/lib/ai/translate-document.js +16 -1
- package/dist/lib/ai/translate-document.js.map +1 -1
- package/dist/lib/ai/types.d.ts +11 -6
- package/dist/lib/ai/types.js.map +1 -1
- package/dist/lib/ai/update-fields.d.ts +8 -6
- package/dist/lib/ai/update-fields.js +9 -22
- package/dist/lib/ai/update-fields.js.map +1 -1
- package/dist/lib/clipboard/paste-issues.d.ts +5 -0
- package/dist/lib/clipboard/paste-issues.js +16 -6
- package/dist/lib/clipboard/paste-issues.js.map +1 -1
- package/dist/lib/cms/document-gate.d.ts +37 -0
- package/dist/lib/cms/document-gate.js +40 -0
- package/dist/lib/cms/document-gate.js.map +1 -0
- package/dist/lib/cms-views/view-model.d.ts +5 -0
- package/dist/lib/cms-views/view-model.js +10 -4
- package/dist/lib/cms-views/view-model.js.map +1 -1
- package/dist/lib/data/collection-content.js +1 -3
- package/dist/lib/data/collection-content.js.map +1 -1
- package/dist/lib/i18n/i18n.d.ts +51 -16
- package/dist/lib/i18n/i18n.js +119 -43
- package/dist/lib/i18n/i18n.js.map +1 -1
- package/dist/lib/i18n/index.d.ts +8 -1
- package/dist/lib/i18n/index.js +1 -1
- package/dist/lib/i18n/index.js.map +1 -1
- package/dist/lib/i18n/messages.de.js +68 -0
- package/dist/lib/i18n/messages.de.js.map +1 -1
- package/dist/lib/i18n/messages.en.d.ts +59 -0
- package/dist/lib/i18n/messages.en.js +80 -0
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/lib/i18n/types.d.ts +15 -9
- package/dist/lib/i18n/types.js.map +1 -1
- package/dist/lib/operations/studio-operations.js +19 -2
- package/dist/lib/operations/studio-operations.js.map +1 -1
- package/dist/lib/persistence/deployment-readiness.js +10 -0
- package/dist/lib/persistence/deployment-readiness.js.map +1 -1
- package/dist/lib/persona/labels.d.ts +44 -9
- package/dist/lib/persona/labels.js +143 -60
- package/dist/lib/persona/labels.js.map +1 -1
- package/dist/lib/webhooks/dispatcher.d.ts +5 -2
- package/dist/lib/webhooks/dispatcher.js.map +1 -1
- package/dist/lib/webhooks/tags.js +150 -17
- package/dist/lib/webhooks/tags.js.map +1 -1
- package/dist/lib/workspace/config-issues.d.ts +3 -1
- package/dist/lib/workspace/config-issues.js +3 -1
- package/dist/lib/workspace/config-issues.js.map +1 -1
- package/dist/lib/workspace/index.d.ts +15 -8
- package/dist/lib/workspace/index.js +17 -18
- package/dist/lib/workspace/index.js.map +1 -1
- package/dist/lib/workspace/workspace-source.js +27 -0
- package/dist/lib/workspace/workspace-source.js.map +1 -1
- package/dist/previews/manifest-format.d.ts +13 -6
- package/dist/previews/manifest-format.js +9 -2
- package/dist/previews/manifest-format.js.map +1 -1
- package/dist/router/root.d.ts +36 -1
- package/dist/router/root.js +54 -7
- package/dist/router/root.js.map +1 -1
- package/package.json +2 -2
package/dist/lib/i18n/types.d.ts
CHANGED
|
@@ -32,16 +32,22 @@ export interface StudioI18nConfig {
|
|
|
32
32
|
*/
|
|
33
33
|
defaultLocale?: string;
|
|
34
34
|
/**
|
|
35
|
-
* **Pin** the studio UI to a fixed locale, overriding browser detection.
|
|
36
|
-
*
|
|
35
|
+
* **Pin** the studio UI to a fixed locale, overriding browser detection. **This is the
|
|
36
|
+
* supported way to choose the studio's language** (EC-513) — set it for any deployment
|
|
37
|
+
* whose editors work in one language, which is what both production consumers do.
|
|
37
38
|
*
|
|
38
|
-
* Why it
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
39
|
+
* Why prefer it: browser detection reads `navigator.languages`, which is **client-only**.
|
|
40
|
+
* The server renders the default locale, and the studio therefore boots in that locale
|
|
41
|
+
* and hands over to the browser's preference only after hydration — correct, but it
|
|
42
|
+
* means a moment of default-locale chrome on a cold load. A pin is read from *config*,
|
|
43
|
+
* imported identically on server and client, so the right language is there from the
|
|
44
|
+
* first byte.
|
|
45
|
+
*
|
|
46
|
+
* There is deliberately **no server-side locale seam** (no `Accept-Language` resolver):
|
|
47
|
+
* locale resolution is client-side, and a pin is how a host states the answer up front.
|
|
48
|
+
*
|
|
49
|
+
* A locale that is not available (neither `defaultLocale` nor a key of `locales`) is
|
|
50
|
+
* ignored with a console signal, and detection resumes. Omit it to follow the browser.
|
|
45
51
|
*/
|
|
46
52
|
locale?: string;
|
|
47
53
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/lib/i18n/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/lib/i18n/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAsDH;;;;GAIG;AACH,MAAM,UAAU,GAAG,CAAC,OAAe,EAAE,WAAoB;IACvD,OAAO,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAA;AAC3E,CAAC","sourcesContent":["/**\n * Studio-UI localization types (EC-241) — the leaf module both the message catalog\n * and the resolver import, so nothing here may import studio runtime code (keeps the\n * `defineStudio` config surface free of import cycles).\n *\n * This is the **studio-chrome** axis (engine-owned strings). Schema labels are a\n * separate mechanism — a {@link import('@elytracms/core/cms-core').LocalizedString}\n * on the field/collection def, resolved via `resolveLabel`. Content-*value*\n * localization is a third, unrelated axis (`cms-core/localization.ts`).\n */\n\n/** A source-catalog entry: the English string + an optional translator hint. */\nexport interface SourceMessage {\n /** The English (source-of-truth) string. */\n message: string\n /**\n * Translator hint/context — the same word can translate differently by context\n * (the standard `msgctxt`/Lingui-comment idea). **Authoring/translation-time only:**\n * it lives on the in-engine source catalog and is NOT shipped in the flat\n * consumer-provided translation catalogs.\n */\n description?: string\n}\n\n/** The in-engine English source catalog: message key → {@link SourceMessage}. */\nexport type SourceCatalog = Record<string, SourceMessage>\n\n/** A consumer-provided translation catalog: flat message key → localized string. */\nexport type MessageCatalog = Record<string, string>\n\n/** The studio-UI localization config passed to `defineStudio({ i18n })` (EC-241). */\nexport interface StudioI18nConfig {\n /**\n * The locale used when the browser's preferred locale matches nothing available.\n * Defaults to `'en'` (the in-engine source language).\n */\n defaultLocale?: string\n /**\n * **Pin** the studio UI to a fixed locale, overriding browser detection. **This is the\n * supported way to choose the studio's language** (EC-513) — set it for any deployment\n * whose editors work in one language, which is what both production consumers do.\n *\n * Why prefer it: browser detection reads `navigator.languages`, which is **client-only**.\n * The server renders the default locale, and the studio therefore boots in that locale\n * and hands over to the browser's preference only after hydration — correct, but it\n * means a moment of default-locale chrome on a cold load. A pin is read from *config*,\n * imported identically on server and client, so the right language is there from the\n * first byte.\n *\n * There is deliberately **no server-side locale seam** (no `Accept-Language` resolver):\n * locale resolution is client-side, and a pin is how a host states the answer up front.\n *\n * A locale that is not available (neither `defaultLocale` nor a key of `locales`) is\n * ignored with a console signal, and detection resumes. Omit it to follow the browser.\n */\n locale?: string\n /**\n * Imported translation catalogs keyed by locale code, e.g. `{ de: deMessages }`.\n * English is always available as the built-in source, so it need not be listed.\n */\n locales?: Record<string, MessageCatalog>\n}\n\n/**\n * Author a source-catalog entry with an optional translator hint (EC-241). Kept in\n * this leaf module so `messages.en.ts` can build the catalog without importing the\n * resolver (which would import studio runtime code).\n */\nexport function msg(message: string, description?: string): SourceMessage {\n return description !== undefined ? { message, description } : { message }\n}\n"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { activeDeploymentKey, selectPersistenceAdapter } from '../persistence/index.js';
|
|
2
2
|
import { attachSessionExpiryReporting } from '../session-expiry.js';
|
|
3
3
|
import { attachConfiguredRevalidationWebhooks } from '../webhooks/index.js';
|
|
4
|
+
import { sessionValidateDocument } from '../cms/document-gate.js';
|
|
4
5
|
import { attachReferenceIndex, createChangesets, createOperations, createRoleAuthorization, } from './operations-package.js';
|
|
5
6
|
/**
|
|
6
7
|
* A no-op {@link ReferenceIndexAttachment} for self-maintaining adapters (EC-277):
|
|
@@ -15,7 +16,17 @@ export function createStudioOperations(adapter, options = {}) {
|
|
|
15
16
|
const authorized = options.actor
|
|
16
17
|
? { actor: options.actor, authorize: createRoleAuthorization(adapter.members) }
|
|
17
18
|
: {};
|
|
18
|
-
|
|
19
|
+
// EC-497: the publish gate validates with the CMS session's canonical option
|
|
20
|
+
// bag — the same one the detail editor, bulk publish, cell edit, import and
|
|
21
|
+
// every AI path use — instead of deciding its own (weaker, mode-dependent)
|
|
22
|
+
// strength off the adapter-served schema record. The seam reads a validator the
|
|
23
|
+
// mounted session published; with no session it returns `undefined` and the gate
|
|
24
|
+
// takes its documented fallback.
|
|
25
|
+
const operations = createOperations({
|
|
26
|
+
adapter,
|
|
27
|
+
validateDocument: sessionValidateDocument,
|
|
28
|
+
...authorized,
|
|
29
|
+
});
|
|
19
30
|
// EC-277: when the adapter maintains the index in its own write path (AD-6 "at the
|
|
20
31
|
// source" — the Convex provider does this server-side, so a raw `convex run` is
|
|
21
32
|
// caught too), the client-side subscriber would only double the work. Attach it
|
|
@@ -25,7 +36,13 @@ export function createStudioOperations(adapter, options = {}) {
|
|
|
25
36
|
const references = adapter.maintainsReferenceIndex
|
|
26
37
|
? noopReferenceIndexAttachment()
|
|
27
38
|
: attachReferenceIndex(operations.events, adapter);
|
|
28
|
-
const changesets = createChangesets({
|
|
39
|
+
const changesets = createChangesets({
|
|
40
|
+
adapter,
|
|
41
|
+
events: operations.events,
|
|
42
|
+
// EC-497: a changeset-driven publish is gated identically — same seam, same bag.
|
|
43
|
+
validateDocument: sessionValidateDocument,
|
|
44
|
+
...authorized,
|
|
45
|
+
});
|
|
29
46
|
// EC-146 instant publish: when a host revalidate endpoint is configured via
|
|
30
47
|
// env, every successful publish-shaped command (document upsert/delete/
|
|
31
48
|
// setState, graph publish/unpublish) fires a signed revalidation webhook.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"studio-operations.js","sourceRoot":"","sources":["../../../src/lib/operations/studio-operations.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PersistenceAdapter } from '@elytracms/core/persistence'\nimport { activeDeploymentKey, selectPersistenceAdapter } from '../persistence'\nimport { attachSessionExpiryReporting } from '../session-expiry'\nimport { attachConfiguredRevalidationWebhooks } from '../webhooks'\nimport {\n attachReferenceIndex,\n createChangesets,\n createOperations,\n createRoleAuthorization,\n} from './operations-package'\nimport type {\n ChangesetOperations,\n OperationsClient,\n Principal,\n ReferenceIndexAttachment,\n} from './operations-package'\n\n/**\n * The studio's single operations bootstrap (EC-141, vision AD-7).\n *\n * One `OperationsClient` + one `ChangesetOperations` instance, built over the\n * selected persistence adapter, shared by every store and surface in the app.\n * The UI never talks to a repository for writes — every mutation is a typed\n * operation command (validate → authorize → mutate → event), so the studio\n * dogfoods the same seam plugins, AI changesets, and the CLI use.\n *\n * The EC-155 reference index is attached to the shared event bus here, so any\n * document/graph/publishing command issued anywhere in the studio keeps the\n * index maintained.\n */\nexport interface StudioOperations {\n readonly operations: OperationsClient\n readonly changesets: ChangesetOperations\n /**\n * The raw adapter, exposed for *transport and read paths only*: blob byte\n * upload/download (`adapter.blobs`) and existing read stores. Studio code\n * must not call repository write methods on it — writes go through\n * `operations`.\n */\n readonly adapter: PersistenceAdapter\n /** EC-155 maintained-on-write index attachment (flushable in tests). */\n readonly references: ReferenceIndexAttachment\n}\n\nexport interface CreateStudioOperationsOptions {\n /**\n * The signed-in principal (EC-150). When present, the EC-150 role hook\n * (`createRoleAuthorization` over `adapter.members`) becomes the\n * authorization chokepoint for every command and query — viewers get\n * structured denials on mutations, editors on administration. Without an\n * actor the client runs pass-through (seeds, fixtures, tests that exercise\n * domain logic rather than permissions); the route guard keeps\n * unauthenticated users away from every operation surface.\n */\n actor?: Principal\n}\n\n/**\n * A no-op {@link ReferenceIndexAttachment} for self-maintaining adapters (EC-277):\n * nothing is subscribed, so `detach` and `flush` are trivially satisfied (the index\n * is kept current in the adapter's write path, not by a queued subscriber).\n */\nfunction noopReferenceIndexAttachment(): ReferenceIndexAttachment {\n return { detach: () => {}, flush: async () => {} }\n}\n\n/** Build a studio operations bundle over an adapter (exported for tests). */\nexport function createStudioOperations(\n adapter: PersistenceAdapter,\n options: CreateStudioOperationsOptions = {},\n): StudioOperations {\n const authorized = options.actor\n ? { actor: options.actor, authorize: createRoleAuthorization(adapter.members) }\n : {}\n const operations = createOperations({ adapter, ...authorized })\n // EC-277: when the adapter maintains the index in its own write path (AD-6 \"at the\n // source\" — the Convex provider does this server-side, so a raw `convex run` is\n // caught too), the client-side subscriber would only double the work. Attach it\n // ONLY for providers that don't self-maintain. Where it does run, extraction is a\n // schema-free, manifest-free walk over self-describing content, so it needs no\n // registry (compositions self-typed their relation/asset props on write).\n const references = adapter.maintainsReferenceIndex\n ? noopReferenceIndexAttachment()\n : attachReferenceIndex(operations.events, adapter)\n const changesets = createChangesets({ adapter, events: operations.events, ...authorized })\n // EC-146 instant publish: when a host revalidate endpoint is configured via\n // env, every successful publish-shaped command (document upsert/delete/\n // setState, graph publish/unpublish) fires a signed revalidation webhook.\n // No env (dev/tests) → no-op.\n attachConfiguredRevalidationWebhooks(operations.events)\n // EC-403: session-expiry detection rides the SAME lifecycle bus — every failed\n // command/query/changeset operation is checked against the session-expired\n // predicate here, so expiry raises the ONE app-level blocking re-auth state no\n // matter which surface issued the operation (per-surface banners can't cover\n // surfaces that render no banner).\n attachSessionExpiryReporting(operations.events)\n return { operations, changesets, adapter, references }\n}\n\n// One adapter + one operations bundle per backend (EC-179): keyed by the active\n// deployment so switching to a project on another Convex deployment selects (or\n// lazily builds) that backend's client, rather than reusing the first one.\nconst adaptersByKey = new Map<string, Promise<PersistenceAdapter>>()\nconst instancesByKey = new Map<string, Promise<StudioOperations>>()\nlet currentActor: Principal | null = null\n\n/**\n * The active backend key, tolerant of an unconfigured env: the real app never\n * reaches `getStudioOperations` while misconfigured (the `DeploymentSetupGate` renders a\n * boot error first), but tests inject a bundle directly via the seam below — so\n * a missing backend resolves to one stable key rather than throwing.\n */\nfunction deploymentKey(): string {\n try {\n return activeDeploymentKey()\n } catch {\n return '__unconfigured__'\n }\n}\n\nfunction getAdapter(key: string): Promise<PersistenceAdapter> {\n let adapter = adaptersByKey.get(key)\n if (!adapter) {\n adapter = selectPersistenceAdapter()\n adaptersByKey.set(key, adapter)\n }\n return adapter\n}\n\n/**\n * The shared studio operations client for the active backend (lazily\n * bootstrapped, cached per deployment). In-memory mode this owns the one seeded\n * adapter the whole session mutates; against Convex it owns one client per\n * deployment — the active project (EC-179) selects which.\n */\nexport function getStudioOperations(): Promise<StudioOperations> {\n const key = deploymentKey()\n let instance = instancesByKey.get(key)\n if (!instance) {\n const actor = currentActor\n instance = getAdapter(key).then((adapter) =>\n createStudioOperations(adapter, actor ? { actor } : {}),\n )\n instancesByKey.set(key, instance)\n }\n return instance\n}\n\n/**\n * Bind (or clear) the session principal (EC-150). Called by the auth context\n * on sign-in/sign-out. Rebuilds the operations clients over the *same* adapters\n * — the persisted state (and, in-memory, the whole session's edits) survives;\n * only the authorization identity changes. All cached backends are rebuilt so a\n * later switch picks up the new actor too.\n */\nexport function setStudioSession(actor: Principal | null): void {\n if (currentActor?.id === actor?.id) return // same user (or both null) — keep the clients\n currentActor = actor\n instancesByKey.clear()\n}\n\n/** Test seam: replace (or clear) the singleton for the active backend. */\nexport function setStudioOperationsForTests(next: StudioOperations | null): void {\n if (next) {\n instancesByKey.set(deploymentKey(), Promise.resolve(next))\n return\n }\n instancesByKey.clear()\n adaptersByKey.clear()\n currentActor = null\n}\n"]}
|
|
1
|
+
{"version":3,"file":"studio-operations.js","sourceRoot":"","sources":["../../../src/lib/operations/studio-operations.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PersistenceAdapter } from '@elytracms/core/persistence'\nimport { activeDeploymentKey, selectPersistenceAdapter } from '../persistence'\nimport { attachSessionExpiryReporting } from '../session-expiry'\nimport { attachConfiguredRevalidationWebhooks } from '../webhooks'\nimport { sessionValidateDocument } from '../cms/document-gate'\nimport {\n attachReferenceIndex,\n createChangesets,\n createOperations,\n createRoleAuthorization,\n} from './operations-package'\nimport type {\n ChangesetOperations,\n OperationsClient,\n Principal,\n ReferenceIndexAttachment,\n} from './operations-package'\n\n/**\n * The studio's single operations bootstrap (EC-141, vision AD-7).\n *\n * One `OperationsClient` + one `ChangesetOperations` instance, built over the\n * selected persistence adapter, shared by every store and surface in the app.\n * The UI never talks to a repository for writes — every mutation is a typed\n * operation command (validate → authorize → mutate → event), so the studio\n * dogfoods the same seam plugins, AI changesets, and the CLI use.\n *\n * The EC-155 reference index is attached to the shared event bus here, so any\n * document/graph/publishing command issued anywhere in the studio keeps the\n * index maintained.\n */\nexport interface StudioOperations {\n readonly operations: OperationsClient\n readonly changesets: ChangesetOperations\n /**\n * The raw adapter, exposed for *transport and read paths only*: blob byte\n * upload/download (`adapter.blobs`) and existing read stores. Studio code\n * must not call repository write methods on it — writes go through\n * `operations`.\n */\n readonly adapter: PersistenceAdapter\n /** EC-155 maintained-on-write index attachment (flushable in tests). */\n readonly references: ReferenceIndexAttachment\n}\n\nexport interface CreateStudioOperationsOptions {\n /**\n * The signed-in principal (EC-150). When present, the EC-150 role hook\n * (`createRoleAuthorization` over `adapter.members`) becomes the\n * authorization chokepoint for every command and query — viewers get\n * structured denials on mutations, editors on administration. Without an\n * actor the client runs pass-through (seeds, fixtures, tests that exercise\n * domain logic rather than permissions); the route guard keeps\n * unauthenticated users away from every operation surface.\n */\n actor?: Principal\n}\n\n/**\n * A no-op {@link ReferenceIndexAttachment} for self-maintaining adapters (EC-277):\n * nothing is subscribed, so `detach` and `flush` are trivially satisfied (the index\n * is kept current in the adapter's write path, not by a queued subscriber).\n */\nfunction noopReferenceIndexAttachment(): ReferenceIndexAttachment {\n return { detach: () => {}, flush: async () => {} }\n}\n\n/** Build a studio operations bundle over an adapter (exported for tests). */\nexport function createStudioOperations(\n adapter: PersistenceAdapter,\n options: CreateStudioOperationsOptions = {},\n): StudioOperations {\n const authorized = options.actor\n ? { actor: options.actor, authorize: createRoleAuthorization(adapter.members) }\n : {}\n // EC-497: the publish gate validates with the CMS session's canonical option\n // bag — the same one the detail editor, bulk publish, cell edit, import and\n // every AI path use — instead of deciding its own (weaker, mode-dependent)\n // strength off the adapter-served schema record. The seam reads a validator the\n // mounted session published; with no session it returns `undefined` and the gate\n // takes its documented fallback.\n const operations = createOperations({\n adapter,\n validateDocument: sessionValidateDocument,\n ...authorized,\n })\n // EC-277: when the adapter maintains the index in its own write path (AD-6 \"at the\n // source\" — the Convex provider does this server-side, so a raw `convex run` is\n // caught too), the client-side subscriber would only double the work. Attach it\n // ONLY for providers that don't self-maintain. Where it does run, extraction is a\n // schema-free, manifest-free walk over self-describing content, so it needs no\n // registry (compositions self-typed their relation/asset props on write).\n const references = adapter.maintainsReferenceIndex\n ? noopReferenceIndexAttachment()\n : attachReferenceIndex(operations.events, adapter)\n const changesets = createChangesets({\n adapter,\n events: operations.events,\n // EC-497: a changeset-driven publish is gated identically — same seam, same bag.\n validateDocument: sessionValidateDocument,\n ...authorized,\n })\n // EC-146 instant publish: when a host revalidate endpoint is configured via\n // env, every successful publish-shaped command (document upsert/delete/\n // setState, graph publish/unpublish) fires a signed revalidation webhook.\n // No env (dev/tests) → no-op.\n attachConfiguredRevalidationWebhooks(operations.events)\n // EC-403: session-expiry detection rides the SAME lifecycle bus — every failed\n // command/query/changeset operation is checked against the session-expired\n // predicate here, so expiry raises the ONE app-level blocking re-auth state no\n // matter which surface issued the operation (per-surface banners can't cover\n // surfaces that render no banner).\n attachSessionExpiryReporting(operations.events)\n return { operations, changesets, adapter, references }\n}\n\n// One adapter + one operations bundle per backend (EC-179): keyed by the active\n// deployment so switching to a project on another Convex deployment selects (or\n// lazily builds) that backend's client, rather than reusing the first one.\nconst adaptersByKey = new Map<string, Promise<PersistenceAdapter>>()\nconst instancesByKey = new Map<string, Promise<StudioOperations>>()\nlet currentActor: Principal | null = null\n\n/**\n * The active backend key, tolerant of an unconfigured env: the real app never\n * reaches `getStudioOperations` while misconfigured (the `DeploymentSetupGate` renders a\n * boot error first), but tests inject a bundle directly via the seam below — so\n * a missing backend resolves to one stable key rather than throwing.\n */\nfunction deploymentKey(): string {\n try {\n return activeDeploymentKey()\n } catch {\n return '__unconfigured__'\n }\n}\n\nfunction getAdapter(key: string): Promise<PersistenceAdapter> {\n let adapter = adaptersByKey.get(key)\n if (!adapter) {\n adapter = selectPersistenceAdapter()\n adaptersByKey.set(key, adapter)\n }\n return adapter\n}\n\n/**\n * The shared studio operations client for the active backend (lazily\n * bootstrapped, cached per deployment). In-memory mode this owns the one seeded\n * adapter the whole session mutates; against Convex it owns one client per\n * deployment — the active project (EC-179) selects which.\n */\nexport function getStudioOperations(): Promise<StudioOperations> {\n const key = deploymentKey()\n let instance = instancesByKey.get(key)\n if (!instance) {\n const actor = currentActor\n instance = getAdapter(key).then((adapter) =>\n createStudioOperations(adapter, actor ? { actor } : {}),\n )\n instancesByKey.set(key, instance)\n }\n return instance\n}\n\n/**\n * Bind (or clear) the session principal (EC-150). Called by the auth context\n * on sign-in/sign-out. Rebuilds the operations clients over the *same* adapters\n * — the persisted state (and, in-memory, the whole session's edits) survives;\n * only the authorization identity changes. All cached backends are rebuilt so a\n * later switch picks up the new actor too.\n */\nexport function setStudioSession(actor: Principal | null): void {\n if (currentActor?.id === actor?.id) return // same user (or both null) — keep the clients\n currentActor = actor\n instancesByKey.clear()\n}\n\n/** Test seam: replace (or clear) the singleton for the active backend. */\nexport function setStudioOperationsForTests(next: StudioOperations | null): void {\n if (next) {\n instancesByKey.set(deploymentKey(), Promise.resolve(next))\n return\n }\n instancesByKey.clear()\n adaptersByKey.clear()\n currentActor = null\n}\n"]}
|
|
@@ -2,6 +2,16 @@ import { checkDeploymentReady, expectedDeploymentEnv } from '@elytracms/core/exp
|
|
|
2
2
|
/** Map the studio's resolved state onto the core readiness facts (pure). */
|
|
3
3
|
export function studioReadinessFacts(inputs) {
|
|
4
4
|
const { backend, workspace, collections, reachable, effectiveUrl } = inputs;
|
|
5
|
+
// The `config` fact carries `issueCount` only on the INVALID branch — deliberately,
|
|
6
|
+
// not by omission (EC-506, decided 2026-08-01). `elytra doctor` sets it on the valid
|
|
7
|
+
// branch too (`cli/src/doctor.js` `configFactsFromOutcome`), which is what makes
|
|
8
|
+
// `deployment-ready.ts`'s `config-valid` `warn` branch reachable there and not here.
|
|
9
|
+
// The studio cannot supply it: `WorkspaceState`'s `config` variant carries no issues,
|
|
10
|
+
// deliberately (see `lib/workspace/workspace-source.ts` for the full argument and the
|
|
11
|
+
// resurface trigger). The divergence is LATENT, not live — the resolver has no
|
|
12
|
+
// warning-severity producer, so on a valid config `issues.length` is 0 and both tools
|
|
13
|
+
// render the same `ok`. When the first config warning exists, close this with the
|
|
14
|
+
// rest of EC-506, not on its own.
|
|
5
15
|
const config = workspace.kind === 'invalid'
|
|
6
16
|
? { valid: false, source: workspace.source, issueCount: workspace.issues.length }
|
|
7
17
|
: workspace.kind === 'config'
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deployment-readiness.js","sourceRoot":"","sources":["../../../src/lib/persistence/deployment-readiness.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,8CAA8C,CAAA;AA2C1G,4EAA4E;AAC5E,MAAM,UAAU,oBAAoB,CAAC,MAA6B;IAChE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,YAAY,EAAE,GAAG,MAAM,CAAA;IAE3E,MAAM,MAAM,GACV,SAAS,CAAC,IAAI,KAAK,SAAS;QAC1B,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE;QACjF,CAAC,CAAC,SAAS,CAAC,IAAI,KAAK,QAAQ;YAC3B,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE;YAC3C,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;IAEvB,MAAM,WAAW,GACf,OAAO,CAAC,IAAI,KAAK,QAAQ;QACvB,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,YAAY,IAAI,OAAO,CAAC,GAAG,EAAE;QACtD,CAAC,CAAC,OAAO,CAAC,IAAI,KAAK,YAAY;YAC7B,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE;YACxB,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAA;IAExD,6EAA6E;IAC7E,6EAA6E;IAC7E,+EAA+E;IAC/E,8EAA8E;IAC9E,kFAAkF;IAClF,MAAM,aAAa,GACjB,WAAW,CAAC,IAAI,KAAK,QAAQ;QAC3B,CAAC,CAAC;YACE,QAAQ,EAAE,qBAAqB,CAAC;gBAC9B,iBAAiB,EAAE,MAAM,CAAC,iBAAiB,IAAI,KAAK;gBACpD,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAChE,CAAC;YACF,iBAAiB,EAAE,kFAAkF;SACtG;QACH,CAAC,CAAC,SAAS,CAAA;IAEf,OAAO;QACL,OAAO,EAAE,WAAW;QACpB,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACnC,WAAW;QACX,MAAM;QACN,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC5C,CAAA;AACH,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,yBAAyB,CAAC,MAA6B;IACrE,OAAO,oBAAoB,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC,CAAA;AAC3D,CAAC","sourcesContent":["import { checkDeploymentReady, expectedDeploymentEnv } from '@elytracms/core/export-sync/deployment-ready'\nimport type {\n DeclaredEnvVar,\n DeploymentReadinessFacts,\n DeploymentReadinessReport,\n} from '@elytracms/core/export-sync/deployment-ready'\nimport type { CollectionDef } from '@elytracms/core/cms-core'\nimport type { StudioBackendConfig } from './backend-mode'\nimport type { WorkspaceState } from '../workspace'\n\n/**\n * Studio-side adapter for the shared `checkDeploymentReady` contract (EC-258).\n * Maps the studio's resolved state — backend mode (`resolveBackendConfig`),\n * workspace config (`getWorkspaceState`), config-owned collections\n * (`resolveDeploymentCollections`), and an optional reachability probe outcome —\n * onto the core-owned {@link DeploymentReadinessFacts}, then composes the report.\n *\n * A PURE mapper: the async I/O (the `adapter.status()` reachability probe) lives in\n * the boot `DeploymentSetupGate`, which passes the outcome in. `authKeys` is\n * deliberately left unprobed here — the deployment's JWKS endpoint is CORS-blocked\n * from the browser, so the auth-signing-keys check resolves to `unknown` at boot\n * (EC-257's honest sign-in error is the browser's actionable surface; the\n * `elytra doctor` CLI probes it definitively from node). `session` is left unset —\n * this gate runs before auth, so the per-user bucket stays `unknown` (never a false\n * fail) until a signed-in surface supplies it.\n */\nexport interface StudioReadinessInputs {\n backend: StudioBackendConfig\n /** The active project's effective deployment URL (`resolveDeploymentUrl`); defaults to `backend.url`. */\n effectiveUrl?: string\n workspace: WorkspaceState\n collections: readonly CollectionDef[]\n /** Reachability probe outcome (`adapter.status()`); omitted when not probed (playground / unconfigured). */\n reachable?: { available: boolean; detail?: string }\n /**\n * Whether `defineStudio({ ai })` is configured and enabled (EC-399) — drives the\n * AI provider-key expectation in the deployment-env checks.\n */\n aiRouteConfigured?: boolean\n /** Consumer-declared deployment-env expectations (EC-399) — merged into the engine contract. */\n declaredEnv?: readonly DeclaredEnvVar[]\n}\n\n/** Map the studio's resolved state onto the core readiness facts (pure). */\nexport function studioReadinessFacts(inputs: StudioReadinessInputs): DeploymentReadinessFacts {\n const { backend, workspace, collections, reachable, effectiveUrl } = inputs\n\n const config: DeploymentReadinessFacts['config'] =\n workspace.kind === 'invalid'\n ? { valid: false, source: workspace.source, issueCount: workspace.issues.length }\n : workspace.kind === 'config'\n ? { valid: true, source: workspace.source }\n : { valid: true }\n\n const backendFact: DeploymentReadinessFacts['backend'] =\n backend.kind === 'convex'\n ? { kind: 'convex', url: effectiveUrl ?? backend.url }\n : backend.kind === 'playground'\n ? { kind: 'playground' }\n : { kind: 'unconfigured', detail: backend.detail }\n\n // Expected-env facts (EC-399): the studio surfaces the SAME contract the CLI\n // checks, but the browser can read neither the Convex deployment env nor the\n // studio host's server env (the AI key is deliberately never VITE_-prefixed) —\n // so no presence source is gathered and every env check surfaces as an honest\n // `unknown` pointing at `elytra doctor`, exactly the auth-signing-keys precedent.\n const deploymentEnv: DeploymentReadinessFacts['deploymentEnv'] =\n backendFact.kind === 'convex'\n ? {\n expected: expectedDeploymentEnv({\n aiRouteConfigured: inputs.aiRouteConfigured ?? false,\n ...(inputs.declaredEnv ? { declared: inputs.declaredEnv } : {}),\n }),\n unavailableDetail: 'Not readable from the browser — run `elytra doctor` to check it (presence only).',\n }\n : undefined\n\n return {\n backend: backendFact,\n ...(reachable ? { reachable } : {}),\n collections,\n config,\n ...(deploymentEnv ? { deploymentEnv } : {}),\n }\n}\n\n/** Compose the studio's deployment-readiness report (pure). */\nexport function studioDeploymentReadiness(inputs: StudioReadinessInputs): DeploymentReadinessReport {\n return checkDeploymentReady(studioReadinessFacts(inputs))\n}\n"]}
|
|
1
|
+
{"version":3,"file":"deployment-readiness.js","sourceRoot":"","sources":["../../../src/lib/persistence/deployment-readiness.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,8CAA8C,CAAA;AA2C1G,4EAA4E;AAC5E,MAAM,UAAU,oBAAoB,CAAC,MAA6B;IAChE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,YAAY,EAAE,GAAG,MAAM,CAAA;IAE3E,oFAAoF;IACpF,qFAAqF;IACrF,iFAAiF;IACjF,qFAAqF;IACrF,sFAAsF;IACtF,sFAAsF;IACtF,+EAA+E;IAC/E,sFAAsF;IACtF,kFAAkF;IAClF,kCAAkC;IAClC,MAAM,MAAM,GACV,SAAS,CAAC,IAAI,KAAK,SAAS;QAC1B,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE;QACjF,CAAC,CAAC,SAAS,CAAC,IAAI,KAAK,QAAQ;YAC3B,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE;YAC3C,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;IAEvB,MAAM,WAAW,GACf,OAAO,CAAC,IAAI,KAAK,QAAQ;QACvB,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,YAAY,IAAI,OAAO,CAAC,GAAG,EAAE;QACtD,CAAC,CAAC,OAAO,CAAC,IAAI,KAAK,YAAY;YAC7B,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE;YACxB,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAA;IAExD,6EAA6E;IAC7E,6EAA6E;IAC7E,+EAA+E;IAC/E,8EAA8E;IAC9E,kFAAkF;IAClF,MAAM,aAAa,GACjB,WAAW,CAAC,IAAI,KAAK,QAAQ;QAC3B,CAAC,CAAC;YACE,QAAQ,EAAE,qBAAqB,CAAC;gBAC9B,iBAAiB,EAAE,MAAM,CAAC,iBAAiB,IAAI,KAAK;gBACpD,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAChE,CAAC;YACF,iBAAiB,EAAE,kFAAkF;SACtG;QACH,CAAC,CAAC,SAAS,CAAA;IAEf,OAAO;QACL,OAAO,EAAE,WAAW;QACpB,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACnC,WAAW;QACX,MAAM;QACN,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC5C,CAAA;AACH,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,yBAAyB,CAAC,MAA6B;IACrE,OAAO,oBAAoB,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC,CAAA;AAC3D,CAAC","sourcesContent":["import { checkDeploymentReady, expectedDeploymentEnv } from '@elytracms/core/export-sync/deployment-ready'\nimport type {\n DeclaredEnvVar,\n DeploymentReadinessFacts,\n DeploymentReadinessReport,\n} from '@elytracms/core/export-sync/deployment-ready'\nimport type { CollectionDef } from '@elytracms/core/cms-core'\nimport type { StudioBackendConfig } from './backend-mode'\nimport type { WorkspaceState } from '../workspace'\n\n/**\n * Studio-side adapter for the shared `checkDeploymentReady` contract (EC-258).\n * Maps the studio's resolved state — backend mode (`resolveBackendConfig`),\n * workspace config (`getWorkspaceState`), config-owned collections\n * (`resolveDeploymentCollections`), and an optional reachability probe outcome —\n * onto the core-owned {@link DeploymentReadinessFacts}, then composes the report.\n *\n * A PURE mapper: the async I/O (the `adapter.status()` reachability probe) lives in\n * the boot `DeploymentSetupGate`, which passes the outcome in. `authKeys` is\n * deliberately left unprobed here — the deployment's JWKS endpoint is CORS-blocked\n * from the browser, so the auth-signing-keys check resolves to `unknown` at boot\n * (EC-257's honest sign-in error is the browser's actionable surface; the\n * `elytra doctor` CLI probes it definitively from node). `session` is left unset —\n * this gate runs before auth, so the per-user bucket stays `unknown` (never a false\n * fail) until a signed-in surface supplies it.\n */\nexport interface StudioReadinessInputs {\n backend: StudioBackendConfig\n /** The active project's effective deployment URL (`resolveDeploymentUrl`); defaults to `backend.url`. */\n effectiveUrl?: string\n workspace: WorkspaceState\n collections: readonly CollectionDef[]\n /** Reachability probe outcome (`adapter.status()`); omitted when not probed (playground / unconfigured). */\n reachable?: { available: boolean; detail?: string }\n /**\n * Whether `defineStudio({ ai })` is configured and enabled (EC-399) — drives the\n * AI provider-key expectation in the deployment-env checks.\n */\n aiRouteConfigured?: boolean\n /** Consumer-declared deployment-env expectations (EC-399) — merged into the engine contract. */\n declaredEnv?: readonly DeclaredEnvVar[]\n}\n\n/** Map the studio's resolved state onto the core readiness facts (pure). */\nexport function studioReadinessFacts(inputs: StudioReadinessInputs): DeploymentReadinessFacts {\n const { backend, workspace, collections, reachable, effectiveUrl } = inputs\n\n // The `config` fact carries `issueCount` only on the INVALID branch — deliberately,\n // not by omission (EC-506, decided 2026-08-01). `elytra doctor` sets it on the valid\n // branch too (`cli/src/doctor.js` `configFactsFromOutcome`), which is what makes\n // `deployment-ready.ts`'s `config-valid` `warn` branch reachable there and not here.\n // The studio cannot supply it: `WorkspaceState`'s `config` variant carries no issues,\n // deliberately (see `lib/workspace/workspace-source.ts` for the full argument and the\n // resurface trigger). The divergence is LATENT, not live — the resolver has no\n // warning-severity producer, so on a valid config `issues.length` is 0 and both tools\n // render the same `ok`. When the first config warning exists, close this with the\n // rest of EC-506, not on its own.\n const config: DeploymentReadinessFacts['config'] =\n workspace.kind === 'invalid'\n ? { valid: false, source: workspace.source, issueCount: workspace.issues.length }\n : workspace.kind === 'config'\n ? { valid: true, source: workspace.source }\n : { valid: true }\n\n const backendFact: DeploymentReadinessFacts['backend'] =\n backend.kind === 'convex'\n ? { kind: 'convex', url: effectiveUrl ?? backend.url }\n : backend.kind === 'playground'\n ? { kind: 'playground' }\n : { kind: 'unconfigured', detail: backend.detail }\n\n // Expected-env facts (EC-399): the studio surfaces the SAME contract the CLI\n // checks, but the browser can read neither the Convex deployment env nor the\n // studio host's server env (the AI key is deliberately never VITE_-prefixed) —\n // so no presence source is gathered and every env check surfaces as an honest\n // `unknown` pointing at `elytra doctor`, exactly the auth-signing-keys precedent.\n const deploymentEnv: DeploymentReadinessFacts['deploymentEnv'] =\n backendFact.kind === 'convex'\n ? {\n expected: expectedDeploymentEnv({\n aiRouteConfigured: inputs.aiRouteConfigured ?? false,\n ...(inputs.declaredEnv ? { declared: inputs.declaredEnv } : {}),\n }),\n unavailableDetail: 'Not readable from the browser — run `elytra doctor` to check it (presence only).',\n }\n : undefined\n\n return {\n backend: backendFact,\n ...(reachable ? { reachable } : {}),\n collections,\n config,\n ...(deploymentEnv ? { deploymentEnv } : {}),\n }\n}\n\n/** Compose the studio's deployment-readiness report (pure). */\nexport function studioDeploymentReadiness(inputs: StudioReadinessInputs): DeploymentReadinessReport {\n return checkDeploymentReady(studioReadinessFacts(inputs))\n}\n"]}
|
|
@@ -1,16 +1,21 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
1
|
+
import type { CmsIssueCode, CmsIssueSeverity, CmsValidationIssue } from '@elytracms/core/cms-core';
|
|
2
|
+
import type { OperationError, OperationErrorCode, OperationIssue } from '@elytracms/core/operations';
|
|
3
|
+
import type { BuildFragmentError, FragmentParseError } from '@elytracms/core/clipboard';
|
|
4
|
+
import type { ContentQueryError } from '@elytracms/core/content';
|
|
4
5
|
import type { CustomizationIssue } from '@elytracms/core/studio-core';
|
|
5
6
|
import type { ComponentManifest } from '@elytracms/core/component-registry';
|
|
6
|
-
/** Plain-language title for a graph validation issue code. */
|
|
7
|
-
export declare function graphIssueLabel(code: IssueCode): string;
|
|
8
7
|
/**
|
|
9
8
|
* Plain-language title for a CMS validation issue code — localized against the
|
|
10
9
|
* active studio-UI locale (EC-443). A runtime-unknown code (impossible typewise;
|
|
11
10
|
* a cast could smuggle one) degrades to its humanized form, never the raw enum.
|
|
11
|
+
*
|
|
12
|
+
* `severity` is optional and additive (EC-507): pass the issue's own severity and a
|
|
13
|
+
* code with a softer warning title (see {@link CMS_ISSUE_WARNING_LABEL_KEYS}) uses
|
|
14
|
+
* it; omit it, or pass `'error'`, and nothing changes. Every render site passes it —
|
|
15
|
+
* the parameter is optional so that a pure/unit caller naming only a code still
|
|
16
|
+
* compiles, not because a surface may skip it.
|
|
12
17
|
*/
|
|
13
|
-
export declare function cmsIssueLabel(code: CmsIssueCode): string;
|
|
18
|
+
export declare function cmsIssueLabel(code: CmsIssueCode, severity?: CmsIssueSeverity): string;
|
|
14
19
|
/**
|
|
15
20
|
* The localized text of a {@link CustomizationIssue} (EC-478) — the ONE place the
|
|
16
21
|
* studio turns a config-level finding into rendered prose.
|
|
@@ -51,12 +56,42 @@ export declare function customizationIssueMessage(issue: CustomizationIssue): st
|
|
|
51
56
|
* beats a missing key.
|
|
52
57
|
*/
|
|
53
58
|
export declare function cmsValidationIssueMessage(issue: CmsValidationIssue): string;
|
|
54
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* The localized text of ONE {@link OperationIssue} line in the error banner (EC-494).
|
|
61
|
+
*
|
|
62
|
+
* These issues cross a process boundary: `operations/documents.ts` rebuilds a
|
|
63
|
+
* `CmsValidationIssue` as an `OperationIssue` **server-side, with no locale**, so the
|
|
64
|
+
* producer cannot translate and the catalog key rides on the wire instead. A ref-less
|
|
65
|
+
* issue — a Zod input diagnostic, a persistence-layer message, a third-party producer
|
|
66
|
+
* of this published type — keeps its English.
|
|
67
|
+
*/
|
|
68
|
+
export declare function operationIssueMessage(issue: OperationIssue): string;
|
|
69
|
+
/**
|
|
70
|
+
* The localized ENVELOPE sentence of an {@link OperationError} (EC-494) — the line
|
|
71
|
+
* beside the localized badge in `OperationErrorBanner`. Localizing only the issue
|
|
72
|
+
* lines would have left exactly one English sentence under a German badge.
|
|
73
|
+
*/
|
|
74
|
+
export declare function operationErrorMessage(error: OperationError): string;
|
|
75
|
+
/**
|
|
76
|
+
* The localized text of a clipboard fragment failure (EC-494) — parse (`corrupt-json`,
|
|
77
|
+
* `foreign-format`, `unsupported-version`, `invalid-payload`) or build
|
|
78
|
+
* (`node-not-found`, `fixture-not-found`), rendered by the builder's paste notice.
|
|
79
|
+
*/
|
|
80
|
+
export declare function clipboardErrorMessage(error: FragmentParseError | BuildFragmentError): string;
|
|
81
|
+
/**
|
|
82
|
+
* The localized text of a {@link ContentQueryError} (EC-494) — the filter builder's
|
|
83
|
+
* per-clause line and the canvas listing notice, both of which render errors built by
|
|
84
|
+
* `core/content`'s `validateWhere`/`validateSort`/`validateLimit`.
|
|
85
|
+
*/
|
|
86
|
+
export declare function contentQueryErrorMessage(error: ContentQueryError): string;
|
|
87
|
+
/**
|
|
88
|
+
* Plain-language title for an operation failure code — localized against the active
|
|
89
|
+
* studio-UI locale. A runtime-unknown code (impossible typewise; a cast could smuggle
|
|
90
|
+
* one) degrades to the generic title, never the raw enum.
|
|
91
|
+
*/
|
|
55
92
|
export declare function operationErrorLabel(code: OperationErrorCode): string;
|
|
56
93
|
/** Friendly name for a project export target (`nextjs` → `Next.js`). */
|
|
57
94
|
export declare function exportTargetLabel(target: string): string;
|
|
58
|
-
/** Plain-language label for a condition operator (`gte` → `At least`). */
|
|
59
|
-
export declare function conditionOperatorLabel(operator: ConditionOperator): string;
|
|
60
95
|
/** The leaf of a component id, namespace stripped (`base.Heading` → `Heading`). */
|
|
61
96
|
export declare function componentLeafId(componentId: string): string;
|
|
62
97
|
/**
|
|
@@ -6,35 +6,49 @@ import { humanizeName } from '../utils.js';
|
|
|
6
6
|
*
|
|
7
7
|
* A non-technical editor must never see a manifest/binding/path-token/validation
|
|
8
8
|
* identifier on — or one click off — the editor path. The engine speaks in stable
|
|
9
|
-
* machine codes (`
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* machine codes (`cmsIssueCodeSchema`, `operationErrorCodeSchema`) and machine ids
|
|
10
|
+
* (`base.Heading`, `r-blog-slug-en`); this module is the single translation layer the
|
|
11
|
+
* editor-facing surfaces route through.
|
|
12
12
|
*
|
|
13
13
|
* The maps are exhaustive `Record<…Code, string>` over the Zod enums, so adding a
|
|
14
|
-
* code in `@elytracms/core/
|
|
14
|
+
* code in `@elytracms/core/cms-core` or `@elytracms/core/operations`
|
|
15
15
|
* fails typecheck here until it is given an editor-facing label — the persona
|
|
16
|
-
* boundary cannot silently regress.
|
|
16
|
+
* boundary cannot silently regress. A table is only worth that guard while something
|
|
17
|
+
* renders it: EC-509 deleted two that nothing did (see below). Engineers keep the raw
|
|
18
|
+
* code: every surface that
|
|
17
19
|
* shows a label also carries the code in a `title=`/`data-*` attribute (inspectable
|
|
18
20
|
* via hover/devtools, never rendered as visible copy).
|
|
19
21
|
*/
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
22
|
+
/**
|
|
23
|
+
* EC-509 — two label tables were DELETED here rather than localized, because measuring
|
|
24
|
+
* first showed nothing rendered them.
|
|
25
|
+
*
|
|
26
|
+
* `GRAPH_ISSUE_LABELS`/`graphIssueLabel` (16 entries, project-graph `IssueCode`) and
|
|
27
|
+
* `CONDITION_OPERATOR_LABELS`/`conditionOperatorLabel` (8, `ConditionOperator`) had
|
|
28
|
+
* **zero call sites** outside this file and its own test — measured by grep over
|
|
29
|
+
* `packages/` + `apps/`, with `cmsIssueLabel` (4 sites) and `operationErrorLabel` (1)
|
|
30
|
+
* as the known-positive control that the search finds a render site when one exists.
|
|
31
|
+
*
|
|
32
|
+
* The graph table was unreachable structurally, not just unused: the ONE live path from
|
|
33
|
+
* graph validation into the studio is `cms-composition-validators.ts`, which maps a
|
|
34
|
+
* graph issue to `CompositionIssue` — a type with `message`/`severity`/`nodePath` and
|
|
35
|
+
* **no `code` field at all** (`cms-core/validate-document.ts:809`). The code an
|
|
36
|
+
* exhaustiveness guard exists to force a label for cannot reach an editor surface.
|
|
37
|
+
* `ConditionOperator` is live in the runtime renderer's condition gate
|
|
38
|
+
* (`runtime-renderer/render.tsx:48`) but has no studio surface: v1 canvas is static
|
|
39
|
+
* props, editor bindings parked, so no inspector ever shows an operator to name.
|
|
40
|
+
*
|
|
41
|
+
* **This overturns an EC-210 decision** (2026-06-17), which kept `graphIssueLabel` when
|
|
42
|
+
* the validation panel was removed, on the rationale that it was "the EC-192 boundary's
|
|
43
|
+
* exhaustiveness guard, not panel code". That was right then and its premise is false
|
|
44
|
+
* now — the guard guards a boundary with no traffic, so it only forced editor copy for
|
|
45
|
+
* strings no editor can see. Localizing them would have added 24 keys to the same
|
|
46
|
+
* effect. If a surface ever renders these codes it needs labels written for it, and
|
|
47
|
+
* will write them localized from the start.
|
|
48
|
+
*
|
|
49
|
+
* Neither symbol is referenced by either consumer repo (measured read-only) and neither
|
|
50
|
+
* appears in docs.
|
|
51
|
+
*/
|
|
38
52
|
/**
|
|
39
53
|
* EC-443: the CMS issue titles live in the EC-241 message catalog
|
|
40
54
|
* (`cms.issue.<code>`, EN source + shipped de), so the Issues pane / for-review
|
|
@@ -69,46 +83,61 @@ const CMS_ISSUE_LABEL_KEYS = {
|
|
|
69
83
|
'unknown-alias': 'cms.issue.unknown-alias',
|
|
70
84
|
'unfilled-slot': 'cms.issue.unfilled-slot',
|
|
71
85
|
'custom-validation': 'cms.issue.custom-validation',
|
|
86
|
+
'unaddressable-url': 'cms.issue.unaddressable-url',
|
|
72
87
|
'unknown-version': 'cms.issue.unknown-version',
|
|
73
88
|
};
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
89
|
+
/**
|
|
90
|
+
* EC-507 rider — the few codes whose TITLE overstates at `warning` severity.
|
|
91
|
+
*
|
|
92
|
+
* `invalid-field-value` reads "Invalid value", which is right when the value is
|
|
93
|
+
* rejected (wrong type, not among its options) and wrong when it is not: EC-472
|
|
94
|
+
* deliberately STORES `0202-09-18` as a legal ISO date and merely flags the year as
|
|
95
|
+
* probably unintended. A title calling that invalid contradicts the value the editor
|
|
96
|
+
* can plainly see was accepted, on all three surfaces at once.
|
|
97
|
+
*
|
|
98
|
+
* Keyed on SEVERITY rather than on the implausible-year case specifically, because
|
|
99
|
+
* the overstatement belongs to the severity: any future warning under this code has
|
|
100
|
+
* the same property (the value was kept), and the fix then needs no new entry. A
|
|
101
|
+
* sparse `Partial` on purpose — a code with no entry keeps one title for both
|
|
102
|
+
* severities, which is the correct default and keeps
|
|
103
|
+
* {@link CMS_ISSUE_LABEL_KEYS} the single exhaustive map.
|
|
104
|
+
*/
|
|
105
|
+
const CMS_ISSUE_WARNING_LABEL_KEYS = {
|
|
106
|
+
'invalid-field-value': 'cms.issue.invalid-field-value.warning',
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* EC-494: the badge titles live in the EC-241 catalog (`operations.errorLabel.<code>`),
|
|
110
|
+
* exactly as EC-443 moved the CMS issue titles. **Measured before changing it:** this
|
|
111
|
+
* map was a hard-coded English `Record`, so once the EC-494 bodies localized the banner
|
|
112
|
+
* rendered a German sentence under an English badge — the inverse of the bug this
|
|
113
|
+
* ticket exists for, and not what the ticket said was there. Still the exhaustive
|
|
114
|
+
* `Record` over the Zod enum, so a new code fails typecheck until it has a key.
|
|
115
|
+
*/
|
|
116
|
+
const OPERATION_ERROR_LABEL_KEYS = {
|
|
117
|
+
validation: 'operations.errorLabel.validation',
|
|
118
|
+
'not-found': 'operations.errorLabel.not-found',
|
|
119
|
+
conflict: 'operations.errorLabel.conflict',
|
|
120
|
+
'backend-unavailable': 'operations.errorLabel.backend-unavailable',
|
|
121
|
+
unauthorized: 'operations.errorLabel.unauthorized',
|
|
122
|
+
unknown: 'operations.errorLabel.unknown',
|
|
81
123
|
};
|
|
82
124
|
const EXPORT_TARGET_LABELS = {
|
|
83
125
|
nextjs: 'Next.js',
|
|
84
126
|
};
|
|
85
|
-
/**
|
|
86
|
-
* Plain-language labels for condition operators. Exhaustive over
|
|
87
|
-
* `conditionOperatorSchema` — a new operator fails typecheck here until it has an
|
|
88
|
-
* editor-facing label. The raw operator (`eq`, `gte`, …) is a machine token an
|
|
89
|
-
* editor must never read in the inspector.
|
|
90
|
-
*/
|
|
91
|
-
const CONDITION_OPERATOR_LABELS = {
|
|
92
|
-
truthy: 'Is set',
|
|
93
|
-
exists: 'Exists',
|
|
94
|
-
eq: 'Equals',
|
|
95
|
-
neq: 'Does not equal',
|
|
96
|
-
gt: 'Greater than',
|
|
97
|
-
gte: 'At least',
|
|
98
|
-
lt: 'Less than',
|
|
99
|
-
lte: 'At most',
|
|
100
|
-
};
|
|
101
|
-
/** Plain-language title for a graph validation issue code. */
|
|
102
|
-
export function graphIssueLabel(code) {
|
|
103
|
-
return GRAPH_ISSUE_LABELS[code];
|
|
104
|
-
}
|
|
105
127
|
/**
|
|
106
128
|
* Plain-language title for a CMS validation issue code — localized against the
|
|
107
129
|
* active studio-UI locale (EC-443). A runtime-unknown code (impossible typewise;
|
|
108
130
|
* a cast could smuggle one) degrades to its humanized form, never the raw enum.
|
|
131
|
+
*
|
|
132
|
+
* `severity` is optional and additive (EC-507): pass the issue's own severity and a
|
|
133
|
+
* code with a softer warning title (see {@link CMS_ISSUE_WARNING_LABEL_KEYS}) uses
|
|
134
|
+
* it; omit it, or pass `'error'`, and nothing changes. Every render site passes it —
|
|
135
|
+
* the parameter is optional so that a pure/unit caller naming only a code still
|
|
136
|
+
* compiles, not because a surface may skip it.
|
|
109
137
|
*/
|
|
110
|
-
export function cmsIssueLabel(code) {
|
|
111
|
-
const key =
|
|
138
|
+
export function cmsIssueLabel(code, severity) {
|
|
139
|
+
const key = (severity === 'warning' ? CMS_ISSUE_WARNING_LABEL_KEYS[code] : undefined) ??
|
|
140
|
+
CMS_ISSUE_LABEL_KEYS[code];
|
|
112
141
|
return key !== undefined ? translate(key) : humanizeName(code);
|
|
113
142
|
}
|
|
114
143
|
/**
|
|
@@ -159,27 +188,81 @@ export function customizationIssueMessage(issue) {
|
|
|
159
188
|
* beats a missing key.
|
|
160
189
|
*/
|
|
161
190
|
export function cmsValidationIssueMessage(issue) {
|
|
162
|
-
|
|
191
|
+
return resolveMessageRef(issue.messageRef, issue.message);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* EC-494 — the ONE resolver every carried message ref goes through.
|
|
195
|
+
*
|
|
196
|
+
* Four published types now carry the same `{ key, vars?, details? }` reference —
|
|
197
|
+
* `CmsValidationIssue` (EC-487), `CustomizationIssue` (EC-478, via its own `reason`
|
|
198
|
+
* slot), and now `OperationIssue`/`OperationError` and the clipboard + content-query
|
|
199
|
+
* errors. They are separate declarations only because the EC-265 DAG forbids the
|
|
200
|
+
* lower subpaths from importing `cms-core`; the RESOLUTION is one rule, so it lives
|
|
201
|
+
* in one function.
|
|
202
|
+
*
|
|
203
|
+
* `fallback` is the producer's prebuilt English, rendered whenever there is no ref —
|
|
204
|
+
* a third-party producer, a Zod diagnostic, a persistence-layer message, a
|
|
205
|
+
* config-authored `validate` string. A visible English sentence beats a missing key
|
|
206
|
+
* (EC-443).
|
|
207
|
+
*/
|
|
208
|
+
function resolveMessageRef(ref, fallback) {
|
|
163
209
|
if (!ref)
|
|
164
|
-
return
|
|
210
|
+
return fallback;
|
|
165
211
|
const vars = { ...ref.vars };
|
|
166
212
|
for (const [placeholder, nested] of Object.entries(ref.details ?? {})) {
|
|
167
213
|
vars[placeholder] = translate(nested.key, nested.vars);
|
|
168
214
|
}
|
|
169
215
|
return translate(ref.key, vars);
|
|
170
216
|
}
|
|
171
|
-
/**
|
|
217
|
+
/**
|
|
218
|
+
* The localized text of ONE {@link OperationIssue} line in the error banner (EC-494).
|
|
219
|
+
*
|
|
220
|
+
* These issues cross a process boundary: `operations/documents.ts` rebuilds a
|
|
221
|
+
* `CmsValidationIssue` as an `OperationIssue` **server-side, with no locale**, so the
|
|
222
|
+
* producer cannot translate and the catalog key rides on the wire instead. A ref-less
|
|
223
|
+
* issue — a Zod input diagnostic, a persistence-layer message, a third-party producer
|
|
224
|
+
* of this published type — keeps its English.
|
|
225
|
+
*/
|
|
226
|
+
export function operationIssueMessage(issue) {
|
|
227
|
+
return resolveMessageRef(issue.messageRef, issue.message);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* The localized ENVELOPE sentence of an {@link OperationError} (EC-494) — the line
|
|
231
|
+
* beside the localized badge in `OperationErrorBanner`. Localizing only the issue
|
|
232
|
+
* lines would have left exactly one English sentence under a German badge.
|
|
233
|
+
*/
|
|
234
|
+
export function operationErrorMessage(error) {
|
|
235
|
+
return resolveMessageRef(error.messageRef, error.message);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* The localized text of a clipboard fragment failure (EC-494) — parse (`corrupt-json`,
|
|
239
|
+
* `foreign-format`, `unsupported-version`, `invalid-payload`) or build
|
|
240
|
+
* (`node-not-found`, `fixture-not-found`), rendered by the builder's paste notice.
|
|
241
|
+
*/
|
|
242
|
+
export function clipboardErrorMessage(error) {
|
|
243
|
+
return resolveMessageRef(error.messageRef, error.message);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* The localized text of a {@link ContentQueryError} (EC-494) — the filter builder's
|
|
247
|
+
* per-clause line and the canvas listing notice, both of which render errors built by
|
|
248
|
+
* `core/content`'s `validateWhere`/`validateSort`/`validateLimit`.
|
|
249
|
+
*/
|
|
250
|
+
export function contentQueryErrorMessage(error) {
|
|
251
|
+
return resolveMessageRef(error.messageRef, error.message);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Plain-language title for an operation failure code — localized against the active
|
|
255
|
+
* studio-UI locale. A runtime-unknown code (impossible typewise; a cast could smuggle
|
|
256
|
+
* one) degrades to the generic title, never the raw enum.
|
|
257
|
+
*/
|
|
172
258
|
export function operationErrorLabel(code) {
|
|
173
|
-
|
|
259
|
+
const key = OPERATION_ERROR_LABEL_KEYS[code];
|
|
260
|
+
return translate(key ?? OPERATION_ERROR_LABEL_KEYS.unknown);
|
|
174
261
|
}
|
|
175
262
|
/** Friendly name for a project export target (`nextjs` → `Next.js`). */
|
|
176
263
|
export function exportTargetLabel(target) {
|
|
177
264
|
return EXPORT_TARGET_LABELS[target] ?? humanizeName(target);
|
|
178
265
|
}
|
|
179
|
-
/** Plain-language label for a condition operator (`gte` → `At least`). */
|
|
180
|
-
export function conditionOperatorLabel(operator) {
|
|
181
|
-
return CONDITION_OPERATOR_LABELS[operator];
|
|
182
|
-
}
|
|
183
266
|
/** The leaf of a component id, namespace stripped (`base.Heading` → `Heading`). */
|
|
184
267
|
export function componentLeafId(componentId) {
|
|
185
268
|
const dot = componentId.lastIndexOf('.');
|