@elytracms/studio 0.0.81 → 0.0.83
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 +5 -2
|
@@ -21,6 +21,30 @@ import type { DeploymentReadinessReport } from '@elytracms/core/export-sync/depl
|
|
|
21
21
|
export declare function DeploymentSetupGate({ children }: {
|
|
22
22
|
children: ReactNode;
|
|
23
23
|
}): import("react").JSX.Element;
|
|
24
|
+
/**
|
|
25
|
+
* Deterministic boot state while the backend probe is in flight — and, in a
|
|
26
|
+
* CONNECTED build, the body of the prerendered shell itself.
|
|
27
|
+
*
|
|
28
|
+
* **EC-525.** EC-523 removed the last localized string from the shell and proved it
|
|
29
|
+
* against the built artefact — but against the *playground* build, which is the demo's
|
|
30
|
+
* default and the one nobody deploys. Every real studio is connected, so
|
|
31
|
+
* `config.kind === 'convex'` seeds `{ phase: 'checking' }` and *this* is what a
|
|
32
|
+
* production cold load actually bakes: `Checking deployment…`, in the build machine's
|
|
33
|
+
* locale, shown to every visitor whatever theirs is. The class was not closed until
|
|
34
|
+
* both builds satisfied it.
|
|
35
|
+
*
|
|
36
|
+
* Same shape as `SessionLoading` (`router/root.tsx`), for the same reasons, which are
|
|
37
|
+
* written out there: a build-time prerender has no request and therefore no locale, so
|
|
38
|
+
* the shell shows a spinner and an EMPTY `role="status"` region, and `useHydrated`
|
|
39
|
+
* fills it in the visitor's real language once hydration lands. No `aria-label` — that
|
|
40
|
+
* bakes the wrong-language string somewhere a screen-reader user cannot see it flip —
|
|
41
|
+
* and no `aria-busy`, which would suppress the announcement this shape exists to make.
|
|
42
|
+
*
|
|
43
|
+
* Exported so the connected shell's pre-hydration markup is pinnable with
|
|
44
|
+
* `renderToStaticMarkup` — same reason `SessionLoading` is. The built artefact is the
|
|
45
|
+
* real proof; the pin is what fails when someone puts a string back.
|
|
46
|
+
*/
|
|
47
|
+
export declare function DeploymentChecking(): import("react").JSX.Element;
|
|
24
48
|
/**
|
|
25
49
|
* The unified "Deployment setup" screen (EC-258): every failing check + its exact
|
|
26
50
|
* fix. Pure/presentational so it is fixture-reproducible and static-render testable.
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
3
|
+
import { Loader2 } from 'lucide-react';
|
|
3
4
|
import { Button, Card, CardContent, CardHeader, CardTitle, Code, Stack, Text } from '@elytracms/studio/ui';
|
|
4
5
|
import { resolveBackendConfig, selectPersistenceAdapter, studioDeploymentReadiness, } from '@elytracms/studio/lib/persistence';
|
|
5
6
|
import { getWorkspaceState, resolveDeploymentAi, resolveDeploymentCollections, resolveDeploymentUrl, } from '@elytracms/studio/lib/workspace';
|
|
6
|
-
import { translate } from '@elytracms/studio/lib/i18n';
|
|
7
|
+
import { translate, useHydrated, useT } from '@elytracms/studio/lib/i18n';
|
|
7
8
|
/**
|
|
8
9
|
* Deployment-setup boot gate (EC-258) — the single readiness surface that
|
|
9
10
|
* supersedes the separate `BackendGate` (missing `VITE_CONVEX_URL` / unreachable)
|
|
@@ -82,9 +83,33 @@ function blockingFailures(report) {
|
|
|
82
83
|
return report.checks.filter((c) => c.status === 'fail' &&
|
|
83
84
|
(c.bucket === 'deployment' || c.bucket === 'deployment-env' || c.bucket === 'app'));
|
|
84
85
|
}
|
|
85
|
-
/**
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
/**
|
|
87
|
+
* Deterministic boot state while the backend probe is in flight — and, in a
|
|
88
|
+
* CONNECTED build, the body of the prerendered shell itself.
|
|
89
|
+
*
|
|
90
|
+
* **EC-525.** EC-523 removed the last localized string from the shell and proved it
|
|
91
|
+
* against the built artefact — but against the *playground* build, which is the demo's
|
|
92
|
+
* default and the one nobody deploys. Every real studio is connected, so
|
|
93
|
+
* `config.kind === 'convex'` seeds `{ phase: 'checking' }` and *this* is what a
|
|
94
|
+
* production cold load actually bakes: `Checking deployment…`, in the build machine's
|
|
95
|
+
* locale, shown to every visitor whatever theirs is. The class was not closed until
|
|
96
|
+
* both builds satisfied it.
|
|
97
|
+
*
|
|
98
|
+
* Same shape as `SessionLoading` (`router/root.tsx`), for the same reasons, which are
|
|
99
|
+
* written out there: a build-time prerender has no request and therefore no locale, so
|
|
100
|
+
* the shell shows a spinner and an EMPTY `role="status"` region, and `useHydrated`
|
|
101
|
+
* fills it in the visitor's real language once hydration lands. No `aria-label` — that
|
|
102
|
+
* bakes the wrong-language string somewhere a screen-reader user cannot see it flip —
|
|
103
|
+
* and no `aria-busy`, which would suppress the announcement this shape exists to make.
|
|
104
|
+
*
|
|
105
|
+
* Exported so the connected shell's pre-hydration markup is pinnable with
|
|
106
|
+
* `renderToStaticMarkup` — same reason `SessionLoading` is. The built artefact is the
|
|
107
|
+
* real proof; the pin is what fails when someone puts a string back.
|
|
108
|
+
*/
|
|
109
|
+
export function DeploymentChecking() {
|
|
110
|
+
const hydrated = useHydrated();
|
|
111
|
+
const t = useT();
|
|
112
|
+
return (_jsx(BootScreenFrame, { children: _jsxs("div", { role: "status", children: [_jsx(Loader2, { className: "size-6 animate-spin text-muted-foreground", "aria-hidden": "true" }), hydrated ? _jsx("span", { className: "sr-only", children: t('shell.deploymentSetup.checking') }) : null] }) }));
|
|
88
113
|
}
|
|
89
114
|
/**
|
|
90
115
|
* The unified "Deployment setup" screen (EC-258): every failing check + its exact
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deployment-setup-gate.js","sourceRoot":"","sources":["../../src/components/deployment-setup-gate.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useMemo, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { Button, Card, CardContent, CardHeader, CardTitle, Code, Stack, Text } from '@elytracms/studio/ui'\nimport {\n resolveBackendConfig,\n selectPersistenceAdapter,\n studioDeploymentReadiness,\n} from '@elytracms/studio/lib/persistence'\nimport {\n getWorkspaceState,\n resolveDeploymentAi,\n resolveDeploymentCollections,\n resolveDeploymentUrl,\n} from '@elytracms/studio/lib/workspace'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport type {\n DeploymentReadinessReport,\n ReadinessCheck,\n} from '@elytracms/core/export-sync/deployment-ready'\n\n/**\n * Deployment-setup boot gate (EC-258) — the single readiness surface that\n * supersedes the separate `BackendGate` (missing `VITE_CONVEX_URL` / unreachable)\n * and `WorkspaceGate` (invalid config) screens by composing the shared\n * `checkDeploymentReady` contract. It renders ONE deterministic \"Deployment setup\"\n * screen listing each failing precondition with its copy-pasteable fix, instead of\n * letting the app boot into a misleading downstream error (most painfully the\n * Konditorei JWT-key footgun). All checks passing → children render untouched.\n *\n * Runs before auth and any adapter is constructed (it is the thing that keeps\n * `selectAuthAdapter`/`selectPersistenceAdapter` from throwing on a misconfigured\n * backend). Convex mode probes reachability once on the client (`adapter.status()`)\n * with a retry; playground and unconfigured resolve synchronously. SSR renders the\n * deterministic \"checking\" state. Auth-signing-keys is reported as `unknown` at boot\n * (the JWKS endpoint is CORS-blocked from the browser — EC-257's honest sign-in error\n * is the browser's actionable surface; `elytra doctor` probes it from node), and the\n * per-user bucket stays `unknown` (this gate runs pre-auth), so neither ever blocks boot.\n */\nexport function DeploymentSetupGate({ children }: { children: ReactNode }) {\n const config = useMemo(() => resolveBackendConfig(), [])\n // EC-399: a configured (and not disabled) `defineStudio({ ai })` block means the\n // AI route exists — its provider-key expectation joins the deployment-env checks.\n const aiRouteConfigured = useMemo(() => {\n const ai = resolveDeploymentAi()\n return Boolean(ai && ai.enabled !== false)\n }, [])\n\n const staticReport = useCallback(\n (): DeploymentReadinessReport =>\n studioDeploymentReadiness({\n backend: config,\n workspace: getWorkspaceState(),\n collections: resolveDeploymentCollections(),\n aiRouteConfigured,\n }),\n [config, aiRouteConfigured],\n )\n\n const [state, setState] = useState<GateState>(() =>\n config.kind === 'convex' ? { phase: 'checking' } : { phase: 'resolved', report: staticReport() },\n )\n\n const check = useCallback(() => {\n if (config.kind !== 'convex') {\n setState({ phase: 'resolved', report: staticReport() })\n return\n }\n const effectiveUrl = resolveDeploymentUrl(config.url)\n const resolve = (reachable: { available: boolean; detail?: string }) =>\n setState({\n phase: 'resolved',\n report: studioDeploymentReadiness({\n backend: config,\n effectiveUrl,\n workspace: getWorkspaceState(),\n collections: resolveDeploymentCollections(),\n reachable,\n aiRouteConfigured,\n }),\n })\n setState({ phase: 'checking' })\n void selectPersistenceAdapter()\n .then((adapter) => adapter.status())\n .then((status) =>\n resolve({ available: status.available, ...(status.detail ? { detail: status.detail } : {}) }),\n )\n .catch((error: unknown) =>\n resolve({ available: false, detail: error instanceof Error ? error.message : String(error) }),\n )\n }, [config, staticReport, aiRouteConfigured])\n\n useEffect(() => {\n if (config.kind === 'convex') check()\n }, [config.kind, check])\n\n if (state.phase === 'checking') return <DeploymentChecking />\n if (blockingFailures(state.report).length > 0) {\n return (\n <DeploymentSetupScreen\n report={state.report}\n onRetry={config.kind === 'convex' ? check : undefined}\n />\n )\n }\n return <>{children}</>\n}\n\ntype GateState = { phase: 'checking' } | { phase: 'resolved'; report: DeploymentReadinessReport }\n\n/**\n * The checks that BLOCK boot: a failed deployment-, deployment-env-, or app-level\n * precondition. The per-user bucket (\"not a member\") is not a boot blocker — it is\n * surfaced post-auth (\"ask an admin\"), and at boot it is `unknown` (pre-auth) anyway.\n * Deployment-env checks are `unknown` in the browser today (nothing gathered — EC-399),\n * so they never block in practice; `warn` findings never block anywhere.\n */\nfunction blockingFailures(report: DeploymentReadinessReport): ReadinessCheck[] {\n return report.checks.filter(\n (c) =>\n c.status === 'fail' &&\n (c.bucket === 'deployment' || c.bucket === 'deployment-env' || c.bucket === 'app'),\n )\n}\n\n/** Deterministic boot state while the backend probe is in flight (also SSR output). */\nfunction DeploymentChecking() {\n return (\n <BootScreenFrame>\n <Text size=\"sm\" tone=\"muted\">\n {translate('shell.deploymentSetup.checking')}\n </Text>\n </BootScreenFrame>\n )\n}\n\n/**\n * The unified \"Deployment setup\" screen (EC-258): every failing check + its exact\n * fix. Pure/presentational so it is fixture-reproducible and static-render testable.\n */\nexport function DeploymentSetupScreen({\n report,\n onRetry,\n}: {\n report: DeploymentReadinessReport\n onRetry?: () => void\n}) {\n const failures = report.checks.filter((c) => c.status === 'fail')\n const envChecks = report.checks.filter((c) => c.bucket === 'deployment-env')\n return (\n <BootScreenFrame>\n <Card className=\"max-w-2xl border-destructive/40\">\n <CardHeader>\n <CardTitle>{translate('shell.deploymentSetup.title')}</CardTitle>\n </CardHeader>\n <CardContent>\n <Stack gap=\"3\">\n <Text size=\"sm\" tone=\"muted\">\n {translate('shell.deploymentSetup.description')}\n </Text>\n <Stack gap=\"2\" role=\"list\" aria-label={translate('shell.deploymentSetup.issuesLabel')}>\n {failures.map((check) => (\n <CheckRow key={check.id} check={check} />\n ))}\n </Stack>\n {envChecks.length > 0 ? <ExpectedEnvSection checks={envChecks} /> : null}\n {onRetry ? (\n <Button variant=\"outline\" size=\"sm\" onClick={onRetry} className=\"self-start\">\n {translate('shell.deploymentSetup.retry')}\n </Button>\n ) : null}\n </Stack>\n </CardContent>\n </Card>\n </BootScreenFrame>\n )\n}\n\n/**\n * The expected-env facts (EC-399) — the SAME contract `elytra doctor` checks, so the\n * studio and CLI tell one story. Presence only: each row is a var name + set/missing/\n * unknown status text; values never exist in the report. In the browser the presence\n * sources aren't readable pre-auth, so rows typically read `unknown` and point at\n * `elytra doctor` — which is exactly the honest answer.\n */\nfunction ExpectedEnvSection({ checks }: { checks: ReadinessCheck[] }) {\n return (\n <Stack gap=\"2\">\n <Text size=\"sm\" className=\"font-medium\">\n {translate('shell.deploymentSetup.envSection')}\n </Text>\n <Text size=\"xs\" tone=\"muted\">\n {translate('shell.deploymentSetup.envSectionHint')}\n </Text>\n <Stack gap=\"1\" role=\"list\" aria-label={translate('shell.deploymentSetup.envSection')}>\n {checks.map((check) => (\n <EnvCheckRow key={check.id} check={check} />\n ))}\n </Stack>\n </Stack>\n )\n}\n\nconst ENV_STATUS_GLYPH: Record<ReadinessCheck['status'], string> = {\n ok: '✓',\n warn: '!',\n fail: '✗',\n unknown: '?',\n}\n\nfunction EnvCheckRow({ check }: { check: ReadinessCheck }) {\n const failing = check.status === 'fail'\n return (\n <div\n role=\"listitem\"\n className={`rounded-md border p-2 ${failing ? 'border-destructive/30 bg-destructive/5' : 'border-border bg-muted/30'}`}\n >\n <Text size=\"xs\" mono className={failing ? 'text-destructive' : undefined}>\n {ENV_STATUS_GLYPH[check.status]} [{check.id}]\n </Text>\n <Text size=\"xs\" tone=\"muted\">\n {check.detail}\n </Text>\n {check.fix && (check.status === 'fail' || check.status === 'warn') ? (\n <Text size=\"xs\" tone=\"muted\" className=\"mt-1\">\n {translate('shell.deploymentSetup.fixLabel')}: <Code className=\"break-all\">{check.fix}</Code>\n </Text>\n ) : null}\n </div>\n )\n}\n\nfunction CheckRow({ check }: { check: ReadinessCheck }) {\n return (\n <div role=\"listitem\" className=\"rounded-md border border-destructive/30 bg-destructive/5 p-2\">\n <Text size=\"xs\" mono className=\"text-destructive\">\n [{check.id}]\n </Text>\n <Text size=\"sm\">{check.detail}</Text>\n {check.fix ? (\n <Text size=\"xs\" tone=\"muted\" className=\"mt-1\">\n {translate('shell.deploymentSetup.fixLabel')}: <Code className=\"break-all\">{check.fix}</Code>\n </Text>\n ) : null}\n </div>\n )\n}\n\nfunction BootScreenFrame({ children }: { children: ReactNode }) {\n return <div className=\"grid min-h-dvh place-items-center bg-background p-6\">{children}</div>\n}\n"]}
|
|
1
|
+
{"version":3,"file":"deployment-setup-gate.js","sourceRoot":"","sources":["../../src/components/deployment-setup-gate.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useMemo, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { Loader2 } from 'lucide-react'\nimport { Button, Card, CardContent, CardHeader, CardTitle, Code, Stack, Text } from '@elytracms/studio/ui'\nimport {\n resolveBackendConfig,\n selectPersistenceAdapter,\n studioDeploymentReadiness,\n} from '@elytracms/studio/lib/persistence'\nimport {\n getWorkspaceState,\n resolveDeploymentAi,\n resolveDeploymentCollections,\n resolveDeploymentUrl,\n} from '@elytracms/studio/lib/workspace'\nimport { translate, useHydrated, useT } from '@elytracms/studio/lib/i18n'\nimport type {\n DeploymentReadinessReport,\n ReadinessCheck,\n} from '@elytracms/core/export-sync/deployment-ready'\n\n/**\n * Deployment-setup boot gate (EC-258) — the single readiness surface that\n * supersedes the separate `BackendGate` (missing `VITE_CONVEX_URL` / unreachable)\n * and `WorkspaceGate` (invalid config) screens by composing the shared\n * `checkDeploymentReady` contract. It renders ONE deterministic \"Deployment setup\"\n * screen listing each failing precondition with its copy-pasteable fix, instead of\n * letting the app boot into a misleading downstream error (most painfully the\n * Konditorei JWT-key footgun). All checks passing → children render untouched.\n *\n * Runs before auth and any adapter is constructed (it is the thing that keeps\n * `selectAuthAdapter`/`selectPersistenceAdapter` from throwing on a misconfigured\n * backend). Convex mode probes reachability once on the client (`adapter.status()`)\n * with a retry; playground and unconfigured resolve synchronously. SSR renders the\n * deterministic \"checking\" state. Auth-signing-keys is reported as `unknown` at boot\n * (the JWKS endpoint is CORS-blocked from the browser — EC-257's honest sign-in error\n * is the browser's actionable surface; `elytra doctor` probes it from node), and the\n * per-user bucket stays `unknown` (this gate runs pre-auth), so neither ever blocks boot.\n */\nexport function DeploymentSetupGate({ children }: { children: ReactNode }) {\n const config = useMemo(() => resolveBackendConfig(), [])\n // EC-399: a configured (and not disabled) `defineStudio({ ai })` block means the\n // AI route exists — its provider-key expectation joins the deployment-env checks.\n const aiRouteConfigured = useMemo(() => {\n const ai = resolveDeploymentAi()\n return Boolean(ai && ai.enabled !== false)\n }, [])\n\n const staticReport = useCallback(\n (): DeploymentReadinessReport =>\n studioDeploymentReadiness({\n backend: config,\n workspace: getWorkspaceState(),\n collections: resolveDeploymentCollections(),\n aiRouteConfigured,\n }),\n [config, aiRouteConfigured],\n )\n\n const [state, setState] = useState<GateState>(() =>\n config.kind === 'convex' ? { phase: 'checking' } : { phase: 'resolved', report: staticReport() },\n )\n\n const check = useCallback(() => {\n if (config.kind !== 'convex') {\n setState({ phase: 'resolved', report: staticReport() })\n return\n }\n const effectiveUrl = resolveDeploymentUrl(config.url)\n const resolve = (reachable: { available: boolean; detail?: string }) =>\n setState({\n phase: 'resolved',\n report: studioDeploymentReadiness({\n backend: config,\n effectiveUrl,\n workspace: getWorkspaceState(),\n collections: resolveDeploymentCollections(),\n reachable,\n aiRouteConfigured,\n }),\n })\n setState({ phase: 'checking' })\n void selectPersistenceAdapter()\n .then((adapter) => adapter.status())\n .then((status) =>\n resolve({ available: status.available, ...(status.detail ? { detail: status.detail } : {}) }),\n )\n .catch((error: unknown) =>\n resolve({ available: false, detail: error instanceof Error ? error.message : String(error) }),\n )\n }, [config, staticReport, aiRouteConfigured])\n\n useEffect(() => {\n if (config.kind === 'convex') check()\n }, [config.kind, check])\n\n if (state.phase === 'checking') return <DeploymentChecking />\n if (blockingFailures(state.report).length > 0) {\n return (\n <DeploymentSetupScreen\n report={state.report}\n onRetry={config.kind === 'convex' ? check : undefined}\n />\n )\n }\n return <>{children}</>\n}\n\ntype GateState = { phase: 'checking' } | { phase: 'resolved'; report: DeploymentReadinessReport }\n\n/**\n * The checks that BLOCK boot: a failed deployment-, deployment-env-, or app-level\n * precondition. The per-user bucket (\"not a member\") is not a boot blocker — it is\n * surfaced post-auth (\"ask an admin\"), and at boot it is `unknown` (pre-auth) anyway.\n * Deployment-env checks are `unknown` in the browser today (nothing gathered — EC-399),\n * so they never block in practice; `warn` findings never block anywhere.\n */\nfunction blockingFailures(report: DeploymentReadinessReport): ReadinessCheck[] {\n return report.checks.filter(\n (c) =>\n c.status === 'fail' &&\n (c.bucket === 'deployment' || c.bucket === 'deployment-env' || c.bucket === 'app'),\n )\n}\n\n/**\n * Deterministic boot state while the backend probe is in flight — and, in a\n * CONNECTED build, the body of the prerendered shell itself.\n *\n * **EC-525.** EC-523 removed the last localized string from the shell and proved it\n * against the built artefact — but against the *playground* build, which is the demo's\n * default and the one nobody deploys. Every real studio is connected, so\n * `config.kind === 'convex'` seeds `{ phase: 'checking' }` and *this* is what a\n * production cold load actually bakes: `Checking deployment…`, in the build machine's\n * locale, shown to every visitor whatever theirs is. The class was not closed until\n * both builds satisfied it.\n *\n * Same shape as `SessionLoading` (`router/root.tsx`), for the same reasons, which are\n * written out there: a build-time prerender has no request and therefore no locale, so\n * the shell shows a spinner and an EMPTY `role=\"status\"` region, and `useHydrated`\n * fills it in the visitor's real language once hydration lands. No `aria-label` — that\n * bakes the wrong-language string somewhere a screen-reader user cannot see it flip —\n * and no `aria-busy`, which would suppress the announcement this shape exists to make.\n *\n * Exported so the connected shell's pre-hydration markup is pinnable with\n * `renderToStaticMarkup` — same reason `SessionLoading` is. The built artefact is the\n * real proof; the pin is what fails when someone puts a string back.\n */\nexport function DeploymentChecking() {\n const hydrated = useHydrated()\n const t = useT()\n return (\n <BootScreenFrame>\n <div role=\"status\">\n <Loader2 className=\"size-6 animate-spin text-muted-foreground\" aria-hidden=\"true\" />\n {hydrated ? <span className=\"sr-only\">{t('shell.deploymentSetup.checking')}</span> : null}\n </div>\n </BootScreenFrame>\n )\n}\n\n/**\n * The unified \"Deployment setup\" screen (EC-258): every failing check + its exact\n * fix. Pure/presentational so it is fixture-reproducible and static-render testable.\n */\nexport function DeploymentSetupScreen({\n report,\n onRetry,\n}: {\n report: DeploymentReadinessReport\n onRetry?: () => void\n}) {\n const failures = report.checks.filter((c) => c.status === 'fail')\n const envChecks = report.checks.filter((c) => c.bucket === 'deployment-env')\n return (\n <BootScreenFrame>\n <Card className=\"max-w-2xl border-destructive/40\">\n <CardHeader>\n <CardTitle>{translate('shell.deploymentSetup.title')}</CardTitle>\n </CardHeader>\n <CardContent>\n <Stack gap=\"3\">\n <Text size=\"sm\" tone=\"muted\">\n {translate('shell.deploymentSetup.description')}\n </Text>\n <Stack gap=\"2\" role=\"list\" aria-label={translate('shell.deploymentSetup.issuesLabel')}>\n {failures.map((check) => (\n <CheckRow key={check.id} check={check} />\n ))}\n </Stack>\n {envChecks.length > 0 ? <ExpectedEnvSection checks={envChecks} /> : null}\n {onRetry ? (\n <Button variant=\"outline\" size=\"sm\" onClick={onRetry} className=\"self-start\">\n {translate('shell.deploymentSetup.retry')}\n </Button>\n ) : null}\n </Stack>\n </CardContent>\n </Card>\n </BootScreenFrame>\n )\n}\n\n/**\n * The expected-env facts (EC-399) — the SAME contract `elytra doctor` checks, so the\n * studio and CLI tell one story. Presence only: each row is a var name + set/missing/\n * unknown status text; values never exist in the report. In the browser the presence\n * sources aren't readable pre-auth, so rows typically read `unknown` and point at\n * `elytra doctor` — which is exactly the honest answer.\n */\nfunction ExpectedEnvSection({ checks }: { checks: ReadinessCheck[] }) {\n return (\n <Stack gap=\"2\">\n <Text size=\"sm\" className=\"font-medium\">\n {translate('shell.deploymentSetup.envSection')}\n </Text>\n <Text size=\"xs\" tone=\"muted\">\n {translate('shell.deploymentSetup.envSectionHint')}\n </Text>\n <Stack gap=\"1\" role=\"list\" aria-label={translate('shell.deploymentSetup.envSection')}>\n {checks.map((check) => (\n <EnvCheckRow key={check.id} check={check} />\n ))}\n </Stack>\n </Stack>\n )\n}\n\nconst ENV_STATUS_GLYPH: Record<ReadinessCheck['status'], string> = {\n ok: '✓',\n warn: '!',\n fail: '✗',\n unknown: '?',\n}\n\nfunction EnvCheckRow({ check }: { check: ReadinessCheck }) {\n const failing = check.status === 'fail'\n return (\n <div\n role=\"listitem\"\n className={`rounded-md border p-2 ${failing ? 'border-destructive/30 bg-destructive/5' : 'border-border bg-muted/30'}`}\n >\n <Text size=\"xs\" mono className={failing ? 'text-destructive' : undefined}>\n {ENV_STATUS_GLYPH[check.status]} [{check.id}]\n </Text>\n <Text size=\"xs\" tone=\"muted\">\n {check.detail}\n </Text>\n {check.fix && (check.status === 'fail' || check.status === 'warn') ? (\n <Text size=\"xs\" tone=\"muted\" className=\"mt-1\">\n {translate('shell.deploymentSetup.fixLabel')}: <Code className=\"break-all\">{check.fix}</Code>\n </Text>\n ) : null}\n </div>\n )\n}\n\nfunction CheckRow({ check }: { check: ReadinessCheck }) {\n return (\n <div role=\"listitem\" className=\"rounded-md border border-destructive/30 bg-destructive/5 p-2\">\n <Text size=\"xs\" mono className=\"text-destructive\">\n [{check.id}]\n </Text>\n <Text size=\"sm\">{check.detail}</Text>\n {check.fix ? (\n <Text size=\"xs\" tone=\"muted\" className=\"mt-1\">\n {translate('shell.deploymentSetup.fixLabel')}: <Code className=\"break-all\">{check.fix}</Code>\n </Text>\n ) : null}\n </div>\n )\n}\n\nfunction BootScreenFrame({ children }: { children: ReactNode }) {\n return <div className=\"grid min-h-dvh place-items-center bg-background p-6\">{children}</div>\n}\n"]}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EC-507 — the studio's ONE issue-severity treatment.
|
|
3
|
+
*
|
|
4
|
+
* Three surfaces render a finding by severity: the document Issues pane
|
|
5
|
+
* (`components/cms/issues-pane.tsx`, EC-283), the section-rail foot
|
|
6
|
+
* (`components/nav-issues.tsx`, EC-493) and the AI proposal card
|
|
7
|
+
* (`components/ai/proposal-issues.tsx`, EC-496). They agreed on the treatment but
|
|
8
|
+
* not on where it lived: EC-493 extracted `navIssueSeverityStyle`/`navIssueSeverityIcon`
|
|
9
|
+
* beside the rail, EC-496 imported those `nav`-prefixed cores into a surface with
|
|
10
|
+
* nothing to do with navigation, and the original stayed inlined in the pane — three
|
|
11
|
+
* expressions of one rule, and no obvious home for the fourth surface to import from.
|
|
12
|
+
* This module is that home. The `nav*` names are retired.
|
|
13
|
+
*
|
|
14
|
+
* Deliberately NOT here: the label/copy vocabulary. `cmsIssueLabel`, the message
|
|
15
|
+
* catalog and `issueBodyDescriptor` are the EC-192 persona boundary — a separate
|
|
16
|
+
* concern that happens to be rendered by the same cards. Presentation and copy stay
|
|
17
|
+
* apart.
|
|
18
|
+
*
|
|
19
|
+
* Also not here: the `data-*` attributes each surface emits
|
|
20
|
+
* (`data-nav-issue-severity`, `data-proposal-issue-severity`). Those are per-surface
|
|
21
|
+
* test/inspection hooks, not part of the shared rule.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* The two values every contributing producer publishes — `CmsValidationIssue.severity`,
|
|
25
|
+
* `CustomizationIssue.severity` and the project-graph `ValidationIssue.severity` are all
|
|
26
|
+
* `'error' | 'warning'`. A surface carries the field across rather than re-deriving a
|
|
27
|
+
* local notion of "bad".
|
|
28
|
+
*/
|
|
29
|
+
export type IssueSeverity = 'error' | 'warning';
|
|
30
|
+
/**
|
|
31
|
+
* The per-severity palette, as a pure mapping onto the shared token palette —
|
|
32
|
+
* unit-testable without a DOM (the `statusDotToneClass` pattern).
|
|
33
|
+
*
|
|
34
|
+
* The values are the document Issues pane's, which had them first: destructive
|
|
35
|
+
* border/fill/text for an error, warning border/fill/text for a warning. A surface
|
|
36
|
+
* applies `card` to the finding's container and `text` to at least its glyph; whether
|
|
37
|
+
* the tint also carries to the body copy is the surface's call (the pane keeps its
|
|
38
|
+
* body `muted`, the rail and the proposal card tint the whole row), because that is
|
|
39
|
+
* layout, not severity.
|
|
40
|
+
*/
|
|
41
|
+
export declare function issueSeverityStyle(severity: IssueSeverity): {
|
|
42
|
+
/** Border + fill for the finding's card. */
|
|
43
|
+
card: string;
|
|
44
|
+
/** Icon + message tone. */
|
|
45
|
+
text: string;
|
|
46
|
+
};
|
|
47
|
+
/** The glyph for a severity — `CircleAlert` for an error, `TriangleAlert` for a warning. */
|
|
48
|
+
export declare function issueSeverityIcon(severity: IssueSeverity): import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
|
|
49
|
+
/**
|
|
50
|
+
* The severity ONE aggregate glyph should report for a mixed list: **the highest
|
|
51
|
+
* present**.
|
|
52
|
+
*
|
|
53
|
+
* An icons-only rail may over-state the mildest finding, but it must never
|
|
54
|
+
* under-report a hard error down to an advisory — the reader has no second signal
|
|
55
|
+
* there, and expanding immediately corrects any over-statement.
|
|
56
|
+
*
|
|
57
|
+
* An empty list has no severity to report; callers do not render the widget at all
|
|
58
|
+
* in that case, and `'warning'` is the conservative answer for one that does.
|
|
59
|
+
*/
|
|
60
|
+
export declare function highestIssueSeverity(issues: readonly {
|
|
61
|
+
severity: IssueSeverity;
|
|
62
|
+
}[]): IssueSeverity;
|
|
63
|
+
/**
|
|
64
|
+
* The same rank as ordering: **errors first**, warnings after, stable within each
|
|
65
|
+
* severity.
|
|
66
|
+
*
|
|
67
|
+
* For a surface that shows every finding rather than one glyph. Where the card's
|
|
68
|
+
* primary action commits the change and the reader scans top-down, the finding that
|
|
69
|
+
* should give pause must not sit below one that should merely inform.
|
|
70
|
+
*/
|
|
71
|
+
export declare function orderBySeverity<T extends {
|
|
72
|
+
severity: IssueSeverity;
|
|
73
|
+
}>(issues: readonly T[]): T[];
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { CircleAlert, TriangleAlert } from 'lucide-react';
|
|
2
|
+
/**
|
|
3
|
+
* The per-severity palette, as a pure mapping onto the shared token palette —
|
|
4
|
+
* unit-testable without a DOM (the `statusDotToneClass` pattern).
|
|
5
|
+
*
|
|
6
|
+
* The values are the document Issues pane's, which had them first: destructive
|
|
7
|
+
* border/fill/text for an error, warning border/fill/text for a warning. A surface
|
|
8
|
+
* applies `card` to the finding's container and `text` to at least its glyph; whether
|
|
9
|
+
* the tint also carries to the body copy is the surface's call (the pane keeps its
|
|
10
|
+
* body `muted`, the rail and the proposal card tint the whole row), because that is
|
|
11
|
+
* layout, not severity.
|
|
12
|
+
*/
|
|
13
|
+
export function issueSeverityStyle(severity) {
|
|
14
|
+
return severity === 'error'
|
|
15
|
+
? { card: 'border-destructive/30 bg-destructive/5', text: 'text-destructive' }
|
|
16
|
+
: { card: 'border-warning/30 bg-warning/5', text: 'text-warning' };
|
|
17
|
+
}
|
|
18
|
+
/** The glyph for a severity — `CircleAlert` for an error, `TriangleAlert` for a warning. */
|
|
19
|
+
export function issueSeverityIcon(severity) {
|
|
20
|
+
return severity === 'error' ? CircleAlert : TriangleAlert;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* **The rank rule: `error` outranks `warning`.** Stated once here because two
|
|
24
|
+
* surfaces express it differently — the collapsed rail shows one aggregate glyph
|
|
25
|
+
* ({@link highestIssueSeverity}), the proposal card shows every finding and sorts
|
|
26
|
+
* them ({@link orderBySeverity}) — and a rule expressed twice drifts.
|
|
27
|
+
*/
|
|
28
|
+
function isError(issue) {
|
|
29
|
+
return issue.severity === 'error';
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The severity ONE aggregate glyph should report for a mixed list: **the highest
|
|
33
|
+
* present**.
|
|
34
|
+
*
|
|
35
|
+
* An icons-only rail may over-state the mildest finding, but it must never
|
|
36
|
+
* under-report a hard error down to an advisory — the reader has no second signal
|
|
37
|
+
* there, and expanding immediately corrects any over-statement.
|
|
38
|
+
*
|
|
39
|
+
* An empty list has no severity to report; callers do not render the widget at all
|
|
40
|
+
* in that case, and `'warning'` is the conservative answer for one that does.
|
|
41
|
+
*/
|
|
42
|
+
export function highestIssueSeverity(issues) {
|
|
43
|
+
return issues.some(isError) ? 'error' : 'warning';
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The same rank as ordering: **errors first**, warnings after, stable within each
|
|
47
|
+
* severity.
|
|
48
|
+
*
|
|
49
|
+
* For a surface that shows every finding rather than one glyph. Where the card's
|
|
50
|
+
* primary action commits the change and the reader scans top-down, the finding that
|
|
51
|
+
* should give pause must not sit below one that should merely inform.
|
|
52
|
+
*/
|
|
53
|
+
export function orderBySeverity(issues) {
|
|
54
|
+
return [...issues.filter(isError), ...issues.filter((issue) => !isError(issue))];
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=issue-severity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"issue-severity.js","sourceRoot":"","sources":["../../src/components/issue-severity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAiCzD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAuB;IAMxD,OAAO,QAAQ,KAAK,OAAO;QACzB,CAAC,CAAC,EAAE,IAAI,EAAE,wCAAwC,EAAE,IAAI,EAAE,kBAAkB,EAAE;QAC9E,CAAC,CAAC,EAAE,IAAI,EAAE,gCAAgC,EAAE,IAAI,EAAE,cAAc,EAAE,CAAA;AACtE,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,iBAAiB,CAAC,QAAuB;IACvD,OAAO,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,aAAa,CAAA;AAC3D,CAAC;AAED;;;;;GAKG;AACH,SAAS,OAAO,CAAC,KAAkC;IACjD,OAAO,KAAK,CAAC,QAAQ,KAAK,OAAO,CAAA;AACnC,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,oBAAoB,CAClC,MAA8C;IAE9C,OAAO,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAA;AACnD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAC7B,MAAoB;IAEpB,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AAClF,CAAC","sourcesContent":["import { CircleAlert, TriangleAlert } from 'lucide-react'\n\n/**\n * EC-507 — the studio's ONE issue-severity treatment.\n *\n * Three surfaces render a finding by severity: the document Issues pane\n * (`components/cms/issues-pane.tsx`, EC-283), the section-rail foot\n * (`components/nav-issues.tsx`, EC-493) and the AI proposal card\n * (`components/ai/proposal-issues.tsx`, EC-496). They agreed on the treatment but\n * not on where it lived: EC-493 extracted `navIssueSeverityStyle`/`navIssueSeverityIcon`\n * beside the rail, EC-496 imported those `nav`-prefixed cores into a surface with\n * nothing to do with navigation, and the original stayed inlined in the pane — three\n * expressions of one rule, and no obvious home for the fourth surface to import from.\n * This module is that home. The `nav*` names are retired.\n *\n * Deliberately NOT here: the label/copy vocabulary. `cmsIssueLabel`, the message\n * catalog and `issueBodyDescriptor` are the EC-192 persona boundary — a separate\n * concern that happens to be rendered by the same cards. Presentation and copy stay\n * apart.\n *\n * Also not here: the `data-*` attributes each surface emits\n * (`data-nav-issue-severity`, `data-proposal-issue-severity`). Those are per-surface\n * test/inspection hooks, not part of the shared rule.\n */\n\n/**\n * The two values every contributing producer publishes — `CmsValidationIssue.severity`,\n * `CustomizationIssue.severity` and the project-graph `ValidationIssue.severity` are all\n * `'error' | 'warning'`. A surface carries the field across rather than re-deriving a\n * local notion of \"bad\".\n */\nexport type IssueSeverity = 'error' | 'warning'\n\n/**\n * The per-severity palette, as a pure mapping onto the shared token palette —\n * unit-testable without a DOM (the `statusDotToneClass` pattern).\n *\n * The values are the document Issues pane's, which had them first: destructive\n * border/fill/text for an error, warning border/fill/text for a warning. A surface\n * applies `card` to the finding's container and `text` to at least its glyph; whether\n * the tint also carries to the body copy is the surface's call (the pane keeps its\n * body `muted`, the rail and the proposal card tint the whole row), because that is\n * layout, not severity.\n */\nexport function issueSeverityStyle(severity: IssueSeverity): {\n /** Border + fill for the finding's card. */\n card: string\n /** Icon + message tone. */\n text: string\n} {\n return severity === 'error'\n ? { card: 'border-destructive/30 bg-destructive/5', text: 'text-destructive' }\n : { card: 'border-warning/30 bg-warning/5', text: 'text-warning' }\n}\n\n/** The glyph for a severity — `CircleAlert` for an error, `TriangleAlert` for a warning. */\nexport function issueSeverityIcon(severity: IssueSeverity) {\n return severity === 'error' ? CircleAlert : TriangleAlert\n}\n\n/**\n * **The rank rule: `error` outranks `warning`.** Stated once here because two\n * surfaces express it differently — the collapsed rail shows one aggregate glyph\n * ({@link highestIssueSeverity}), the proposal card shows every finding and sorts\n * them ({@link orderBySeverity}) — and a rule expressed twice drifts.\n */\nfunction isError(issue: { severity: IssueSeverity }): boolean {\n return issue.severity === 'error'\n}\n\n/**\n * The severity ONE aggregate glyph should report for a mixed list: **the highest\n * present**.\n *\n * An icons-only rail may over-state the mildest finding, but it must never\n * under-report a hard error down to an advisory — the reader has no second signal\n * there, and expanding immediately corrects any over-statement.\n *\n * An empty list has no severity to report; callers do not render the widget at all\n * in that case, and `'warning'` is the conservative answer for one that does.\n */\nexport function highestIssueSeverity(\n issues: readonly { severity: IssueSeverity }[],\n): IssueSeverity {\n return issues.some(isError) ? 'error' : 'warning'\n}\n\n/**\n * The same rank as ordering: **errors first**, warnings after, stable within each\n * severity.\n *\n * For a surface that shows every finding rather than one glyph. Where the card's\n * primary action commits the change and the reader scans top-down, the finding that\n * should give pause must not sit below one that should merely inform.\n */\nexport function orderBySeverity<T extends { severity: IssueSeverity }>(\n issues: readonly T[],\n): T[] {\n return [...issues.filter(isError), ...issues.filter((issue) => !isError(issue))]\n}\n"]}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { CmsValidationIssue } from '@elytracms/core/cms-core';
|
|
2
|
+
import type { CustomizationIssue } from '@elytracms/core/studio-core';
|
|
3
|
+
import type { IssueSeverity } from './issue-severity.js';
|
|
4
|
+
/**
|
|
5
|
+
* What the rail foot needs to render one finding: a stable key, a message, and the
|
|
6
|
+
* finding's severity. The least common shape across its contributing sources —
|
|
7
|
+
* customization issues (`CustomizationIssue`) and URL-map validation
|
|
8
|
+
* (`CmsValidationIssue`, EC-469) — so a new source joins by mapping to this, not by
|
|
9
|
+
* pretending to be a customization issue.
|
|
10
|
+
*
|
|
11
|
+
* `severity` was added by EC-493: both mappers used to drop it and the widget painted
|
|
12
|
+
* every finding amber, which stopped being a style nit once EC-488 re-derived
|
|
13
|
+
* `shadowedMountSpaces` from `error` to `warning` ON THE REASONING that the two mean
|
|
14
|
+
* different things, and EC-482 shipped one producer (`hierarchyMountConflicts`)
|
|
15
|
+
* emitting both into this very list.
|
|
16
|
+
*/
|
|
17
|
+
export interface NavIssueItem {
|
|
18
|
+
key: string;
|
|
19
|
+
message: string;
|
|
20
|
+
severity: IssueSeverity;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Customization issues, as rail-foot items — localized (EC-478). The producers
|
|
24
|
+
* (`studio-core/customization.ts`, `default-modules.tsx`, `builder/reuse.ts`) emit a
|
|
25
|
+
* catalog key + identifier values rather than a sentence, because the EC-236 seam
|
|
26
|
+
* lives in `packages/core` and may not import the studio catalog;
|
|
27
|
+
* {@link customizationIssueMessage} resolves them here, so this list renders wholly in
|
|
28
|
+
* the active locale instead of half of it.
|
|
29
|
+
*
|
|
30
|
+
* EC-493: `severity` rides along. It used to be dropped here, which is half of why an
|
|
31
|
+
* error and a warning were indistinguishable at the foot — `CustomizationIssue` has
|
|
32
|
+
* carried the field since EC-236, the mapper simply did not read it.
|
|
33
|
+
*/
|
|
34
|
+
export declare function customizationItems(issues: readonly CustomizationIssue[]): NavIssueItem[];
|
|
35
|
+
/**
|
|
36
|
+
* URL-map validation issues (EC-469), as rail-foot items.
|
|
37
|
+
*
|
|
38
|
+
* Copy follows the issues-pane convention (EC-192): the localized plain-language label,
|
|
39
|
+
* then the detail — both localized since EC-487, which gave `CmsValidationIssue` the
|
|
40
|
+
* same catalog-reference carriage EC-478 gave `CustomizationIssue`.
|
|
41
|
+
*
|
|
42
|
+
* EC-493: `severity` rides along, the other half of the fix. It matters most here:
|
|
43
|
+
* EC-482's `hierarchyMountConflicts` emits `error` for two mounts on the same path and
|
|
44
|
+
* `warning` for a nested pair FROM ONE FUNCTION, and EC-488 re-derived
|
|
45
|
+
* `shadowedMountSpaces` to `warning` precisely because the fall-through made it
|
|
46
|
+
* conditional. Both land in this list.
|
|
47
|
+
*/
|
|
48
|
+
export declare function routeIssueItems(issues: readonly CmsValidationIssue[]): NavIssueItem[];
|
|
49
|
+
/**
|
|
50
|
+
* Visible project-level issues at the rail foot — the seam's "never silent" (EC-236,
|
|
51
|
+
* extended by EC-467's reuse issues, EC-469's URL-map issues and EC-479's config
|
|
52
|
+
* issues).
|
|
53
|
+
*
|
|
54
|
+
* EC-493: an `error` and a `warning` render distinguishably here, in the treatment the
|
|
55
|
+
* document Issues pane already uses. Each finding is its own tinted card (as in the
|
|
56
|
+
* pane) rather than one shared amber box, because a shared box cannot express a mixed
|
|
57
|
+
* list; collapsed, the single glyph follows {@link highestIssueSeverity}. EC-507 moved
|
|
58
|
+
* that treatment to `components/issue-severity.ts`, the one home all three surfaces
|
|
59
|
+
* import from.
|
|
60
|
+
*
|
|
61
|
+
* `data-nav-issue-severity` is on each card (and on the collapsed glyph) as the
|
|
62
|
+
* machine-readable form of the same distinction — what the smoke test pins, and what an
|
|
63
|
+
* agent inspecting the DOM reads without decoding Tailwind classes.
|
|
64
|
+
*/
|
|
65
|
+
export declare function NavIssues({ issues, collapsed }: {
|
|
66
|
+
issues: NavIssueItem[];
|
|
67
|
+
collapsed: boolean;
|
|
68
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from '@elytracms/studio/lib/utils';
|
|
3
|
+
import { translate } from '@elytracms/studio/lib/i18n';
|
|
4
|
+
import { cmsIssueLabel, cmsValidationIssueMessage, customizationIssueMessage, } from '@elytracms/studio/lib/persona/labels';
|
|
5
|
+
import { Row, Stack, Text } from '@elytracms/studio/ui';
|
|
6
|
+
import { highestIssueSeverity, issueSeverityIcon, issueSeverityStyle } from './issue-severity.js';
|
|
7
|
+
/**
|
|
8
|
+
* Customization issues, as rail-foot items — localized (EC-478). The producers
|
|
9
|
+
* (`studio-core/customization.ts`, `default-modules.tsx`, `builder/reuse.ts`) emit a
|
|
10
|
+
* catalog key + identifier values rather than a sentence, because the EC-236 seam
|
|
11
|
+
* lives in `packages/core` and may not import the studio catalog;
|
|
12
|
+
* {@link customizationIssueMessage} resolves them here, so this list renders wholly in
|
|
13
|
+
* the active locale instead of half of it.
|
|
14
|
+
*
|
|
15
|
+
* EC-493: `severity` rides along. It used to be dropped here, which is half of why an
|
|
16
|
+
* error and a warning were indistinguishable at the foot — `CustomizationIssue` has
|
|
17
|
+
* carried the field since EC-236, the mapper simply did not read it.
|
|
18
|
+
*/
|
|
19
|
+
export function customizationItems(issues) {
|
|
20
|
+
return issues.map((issue, index) => ({
|
|
21
|
+
key: `${issue.point}:${issue.id ?? index}`,
|
|
22
|
+
message: customizationIssueMessage(issue),
|
|
23
|
+
severity: issue.severity,
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* URL-map validation issues (EC-469), as rail-foot items.
|
|
28
|
+
*
|
|
29
|
+
* Copy follows the issues-pane convention (EC-192): the localized plain-language label,
|
|
30
|
+
* then the detail — both localized since EC-487, which gave `CmsValidationIssue` the
|
|
31
|
+
* same catalog-reference carriage EC-478 gave `CustomizationIssue`.
|
|
32
|
+
*
|
|
33
|
+
* EC-493: `severity` rides along, the other half of the fix. It matters most here:
|
|
34
|
+
* EC-482's `hierarchyMountConflicts` emits `error` for two mounts on the same path and
|
|
35
|
+
* `warning` for a nested pair FROM ONE FUNCTION, and EC-488 re-derived
|
|
36
|
+
* `shadowedMountSpaces` to `warning` precisely because the fall-through made it
|
|
37
|
+
* conditional. Both land in this list.
|
|
38
|
+
*/
|
|
39
|
+
export function routeIssueItems(issues) {
|
|
40
|
+
return issues.map((issue, index) => ({
|
|
41
|
+
key: `route-issue:${issue.code}:${issue.path.join('/') || index}`,
|
|
42
|
+
message: `${cmsIssueLabel(issue.code, issue.severity)} — ${cmsValidationIssueMessage(issue)}`,
|
|
43
|
+
severity: issue.severity,
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Visible project-level issues at the rail foot — the seam's "never silent" (EC-236,
|
|
48
|
+
* extended by EC-467's reuse issues, EC-469's URL-map issues and EC-479's config
|
|
49
|
+
* issues).
|
|
50
|
+
*
|
|
51
|
+
* EC-493: an `error` and a `warning` render distinguishably here, in the treatment the
|
|
52
|
+
* document Issues pane already uses. Each finding is its own tinted card (as in the
|
|
53
|
+
* pane) rather than one shared amber box, because a shared box cannot express a mixed
|
|
54
|
+
* list; collapsed, the single glyph follows {@link highestIssueSeverity}. EC-507 moved
|
|
55
|
+
* that treatment to `components/issue-severity.ts`, the one home all three surfaces
|
|
56
|
+
* import from.
|
|
57
|
+
*
|
|
58
|
+
* `data-nav-issue-severity` is on each card (and on the collapsed glyph) as the
|
|
59
|
+
* machine-readable form of the same distinction — what the smoke test pins, and what an
|
|
60
|
+
* agent inspecting the DOM reads without decoding Tailwind classes.
|
|
61
|
+
*/
|
|
62
|
+
export function NavIssues({ issues, collapsed }) {
|
|
63
|
+
if (collapsed) {
|
|
64
|
+
const severity = highestIssueSeverity(issues);
|
|
65
|
+
const Icon = issueSeverityIcon(severity);
|
|
66
|
+
return (_jsx("div", { className: cn('grid h-9 place-items-center', issueSeverityStyle(severity).text), "data-nav-issue-severity": severity, title: issues.map((issue) => issue.message).join('\n'), children: _jsx(Icon, { className: "size-4", "aria-label": translate('shell.nav.issuesCount', { count: issues.length }) }) }));
|
|
67
|
+
}
|
|
68
|
+
return (_jsx(Stack, { gap: "1", className: "mb-1", children: issues.map((issue) => (_jsx(NavIssueCard, { issue: issue }, issue.key))) }));
|
|
69
|
+
}
|
|
70
|
+
/** One finding at the rail foot, tinted + glyphed by its severity. */
|
|
71
|
+
function NavIssueCard({ issue }) {
|
|
72
|
+
const style = issueSeverityStyle(issue.severity);
|
|
73
|
+
const Icon = issueSeverityIcon(issue.severity);
|
|
74
|
+
return (_jsxs(Row, { gap: "1", align: "start",
|
|
75
|
+
// `gap-1.5` (the pre-EC-493 icon/text spacing) merged over the primitive's `gap-1`.
|
|
76
|
+
className: cn('gap-1.5 rounded-md border px-2 py-1.5', style.card, style.text), "data-nav-issue-severity": issue.severity, title: issue.message, children: [_jsx(Icon, { className: "mt-0.5 size-3.5 shrink-0", "aria-hidden": true }), _jsx(Text, { size: "xs", className: "min-w-0 leading-snug", children: issue.message })] }));
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=nav-issues.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nav-issues.js","sourceRoot":"","sources":["../../src/components/nav-issues.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { CmsValidationIssue } from '@elytracms/core/cms-core'\nimport type { CustomizationIssue } from '@elytracms/core/studio-core'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport {\n cmsIssueLabel,\n cmsValidationIssueMessage,\n customizationIssueMessage,\n} from '@elytracms/studio/lib/persona/labels'\nimport { Row, Stack, Text } from '@elytracms/studio/ui'\nimport { highestIssueSeverity, issueSeverityIcon, issueSeverityStyle } from './issue-severity'\nimport type { IssueSeverity } from './issue-severity'\n\n/**\n * What the rail foot needs to render one finding: a stable key, a message, and the\n * finding's severity. The least common shape across its contributing sources —\n * customization issues (`CustomizationIssue`) and URL-map validation\n * (`CmsValidationIssue`, EC-469) — so a new source joins by mapping to this, not by\n * pretending to be a customization issue.\n *\n * `severity` was added by EC-493: both mappers used to drop it and the widget painted\n * every finding amber, which stopped being a style nit once EC-488 re-derived\n * `shadowedMountSpaces` from `error` to `warning` ON THE REASONING that the two mean\n * different things, and EC-482 shipped one producer (`hierarchyMountConflicts`)\n * emitting both into this very list.\n */\nexport interface NavIssueItem {\n key: string\n message: string\n severity: IssueSeverity\n}\n\n/**\n * Customization issues, as rail-foot items — localized (EC-478). The producers\n * (`studio-core/customization.ts`, `default-modules.tsx`, `builder/reuse.ts`) emit a\n * catalog key + identifier values rather than a sentence, because the EC-236 seam\n * lives in `packages/core` and may not import the studio catalog;\n * {@link customizationIssueMessage} resolves them here, so this list renders wholly in\n * the active locale instead of half of it.\n *\n * EC-493: `severity` rides along. It used to be dropped here, which is half of why an\n * error and a warning were indistinguishable at the foot — `CustomizationIssue` has\n * carried the field since EC-236, the mapper simply did not read it.\n */\nexport function customizationItems(issues: readonly CustomizationIssue[]): NavIssueItem[] {\n return issues.map((issue, index) => ({\n key: `${issue.point}:${issue.id ?? index}`,\n message: customizationIssueMessage(issue),\n severity: issue.severity,\n }))\n}\n\n/**\n * URL-map validation issues (EC-469), as rail-foot items.\n *\n * Copy follows the issues-pane convention (EC-192): the localized plain-language label,\n * then the detail — both localized since EC-487, which gave `CmsValidationIssue` the\n * same catalog-reference carriage EC-478 gave `CustomizationIssue`.\n *\n * EC-493: `severity` rides along, the other half of the fix. It matters most here:\n * EC-482's `hierarchyMountConflicts` emits `error` for two mounts on the same path and\n * `warning` for a nested pair FROM ONE FUNCTION, and EC-488 re-derived\n * `shadowedMountSpaces` to `warning` precisely because the fall-through made it\n * conditional. Both land in this list.\n */\nexport function routeIssueItems(issues: readonly CmsValidationIssue[]): NavIssueItem[] {\n return issues.map((issue, index) => ({\n key: `route-issue:${issue.code}:${issue.path.join('/') || index}`,\n message: `${cmsIssueLabel(issue.code, issue.severity)} — ${cmsValidationIssueMessage(issue)}`,\n severity: issue.severity,\n }))\n}\n\n/**\n * Visible project-level issues at the rail foot — the seam's \"never silent\" (EC-236,\n * extended by EC-467's reuse issues, EC-469's URL-map issues and EC-479's config\n * issues).\n *\n * EC-493: an `error` and a `warning` render distinguishably here, in the treatment the\n * document Issues pane already uses. Each finding is its own tinted card (as in the\n * pane) rather than one shared amber box, because a shared box cannot express a mixed\n * list; collapsed, the single glyph follows {@link highestIssueSeverity}. EC-507 moved\n * that treatment to `components/issue-severity.ts`, the one home all three surfaces\n * import from.\n *\n * `data-nav-issue-severity` is on each card (and on the collapsed glyph) as the\n * machine-readable form of the same distinction — what the smoke test pins, and what an\n * agent inspecting the DOM reads without decoding Tailwind classes.\n */\nexport function NavIssues({ issues, collapsed }: { issues: NavIssueItem[]; collapsed: boolean }) {\n if (collapsed) {\n const severity = highestIssueSeverity(issues)\n const Icon = issueSeverityIcon(severity)\n return (\n <div\n className={cn('grid h-9 place-items-center', issueSeverityStyle(severity).text)}\n data-nav-issue-severity={severity}\n title={issues.map((issue) => issue.message).join('\\n')}\n >\n <Icon\n className=\"size-4\"\n aria-label={translate('shell.nav.issuesCount', { count: issues.length })}\n />\n </div>\n )\n }\n return (\n <Stack gap=\"1\" className=\"mb-1\">\n {issues.map((issue) => (\n <NavIssueCard key={issue.key} issue={issue} />\n ))}\n </Stack>\n )\n}\n\n/** One finding at the rail foot, tinted + glyphed by its severity. */\nfunction NavIssueCard({ issue }: { issue: NavIssueItem }) {\n const style = issueSeverityStyle(issue.severity)\n const Icon = issueSeverityIcon(issue.severity)\n return (\n <Row\n gap=\"1\"\n align=\"start\"\n // `gap-1.5` (the pre-EC-493 icon/text spacing) merged over the primitive's `gap-1`.\n className={cn('gap-1.5 rounded-md border px-2 py-1.5', style.card, style.text)}\n data-nav-issue-severity={issue.severity}\n title={issue.message}\n >\n <Icon className=\"mt-0.5 size-3.5 shrink-0\" aria-hidden />\n <Text size=\"xs\" className=\"min-w-0 leading-snug\">\n {issue.message}\n </Text>\n </Row>\n )\n}\n"]}
|
|
@@ -4,7 +4,7 @@ import { CircleAlert, X } from 'lucide-react';
|
|
|
4
4
|
import { isSessionExpiredError } from '@elytracms/studio/lib/auth-errors';
|
|
5
5
|
import { reportSessionExpiredError } from '@elytracms/studio/lib/session-expiry';
|
|
6
6
|
import { Badge, Button, Row, Stack, Text } from '@elytracms/studio/ui';
|
|
7
|
-
import { operationErrorLabel } from '@elytracms/studio/lib/persona/labels';
|
|
7
|
+
import { operationErrorLabel, operationErrorMessage, operationIssueMessage, } from '@elytracms/studio/lib/persona/labels';
|
|
8
8
|
import { translate } from '@elytracms/studio/lib/i18n';
|
|
9
9
|
/**
|
|
10
10
|
* Shared error surface for failed/denied operations (EC-141). Every studio
|
|
@@ -29,10 +29,10 @@ export function OperationErrorBanner({ error, onDismiss, className, }) {
|
|
|
29
29
|
}, [error]);
|
|
30
30
|
if (isSessionExpiredError(error))
|
|
31
31
|
return null;
|
|
32
|
-
return (_jsx("div", { role: "alert", className: 'rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 ' + (className ?? ''), children: _jsxs(Row, { gap: "2", align: "start", children: [_jsx(CircleAlert, { className: "mt-0.5 size-4 shrink-0 text-destructive" }), _jsxs(Stack, { gap: "1", className: "min-w-0 flex-1", children: [_jsxs(Row, { gap: "2", align: "center", children: [_jsx(Badge, { variant: "error", title: error.code, "data-code": error.code, children: operationErrorLabel(error.code) }), _jsx(Text, { size: "sm", className: "text-destructive", children: error
|
|
32
|
+
return (_jsx("div", { role: "alert", className: 'rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 ' + (className ?? ''), children: _jsxs(Row, { gap: "2", align: "start", children: [_jsx(CircleAlert, { className: "mt-0.5 size-4 shrink-0 text-destructive" }), _jsxs(Stack, { gap: "1", className: "min-w-0 flex-1", children: [_jsxs(Row, { gap: "2", align: "center", children: [_jsx(Badge, { variant: "error", title: error.code, "data-code": error.code, children: operationErrorLabel(error.code) }), _jsx(Text, { size: "sm", className: "text-destructive", children: operationErrorMessage(error) })] }), error.code === 'validation' && error.issues.length > 0 ? (_jsx(Stack, { gap: "none", children: error.issues.map((issue, i) => (_jsx(Text, { size: "xs", tone: "muted",
|
|
33
33
|
// The raw graph path (`pages.0.root.slots…`) is structural
|
|
34
34
|
// machine addressing — kept inspectable on hover, never shown
|
|
35
35
|
// as visible copy to a non-technical editor (EC-192).
|
|
36
|
-
title: issue.path && issue.path.length > 0 ? issue.path.join('.') : undefined, children: issue
|
|
36
|
+
title: issue.path && issue.path.length > 0 ? issue.path.join('.') : undefined, children: operationIssueMessage(issue) }, `${issue.code}-${i}`))) })) : null] }), onDismiss ? (_jsx(Button, { size: "icon-sm", variant: "ghost", "aria-label": translate('error.dismiss'), onClick: onDismiss, className: "shrink-0", children: _jsx(X, {}) })) : null] }) }));
|
|
37
37
|
}
|
|
38
38
|
//# sourceMappingURL=operation-error.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"operation-error.js","sourceRoot":"","sources":["../../src/components/operation-error.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACjC,OAAO,EAAE,WAAW,EAAE,CAAC,EAAE,MAAM,cAAc,CAAA;AAE7C,OAAO,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAA;AACzE,OAAO,EAAE,yBAAyB,EAAE,MAAM,sCAAsC,CAAA;AAChF,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AACtE,OAAO,
|
|
1
|
+
{"version":3,"file":"operation-error.js","sourceRoot":"","sources":["../../src/components/operation-error.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACjC,OAAO,EAAE,WAAW,EAAE,CAAC,EAAE,MAAM,cAAc,CAAA;AAE7C,OAAO,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAA;AACzE,OAAO,EAAE,yBAAyB,EAAE,MAAM,sCAAsC,CAAA;AAChF,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AACtE,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,sCAAsC,CAAA;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AAEtD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,oBAAoB,CAAC,EACnC,KAAK,EACL,SAAS,EACT,SAAS,GAKV;IACC,uEAAuE;IACvE,yEAAyE;IACzE,kEAAkE;IAClE,0DAA0D;IAC1D,SAAS,CAAC,GAAG,EAAE;QACb,yBAAyB,CAAC,KAAK,CAAC,CAAA;IAClC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAA;IAEX,IAAI,qBAAqB,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IAE7C,OAAO,CACL,cACE,IAAI,EAAC,OAAO,EACZ,SAAS,EACP,sEAAsE,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC,YAG5F,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,KAAK,EAAC,OAAO,aACxB,KAAC,WAAW,IAAC,SAAS,EAAC,yCAAyC,GAAG,EACnE,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,SAAS,EAAC,gBAAgB,aACvC,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,KAAK,EAAC,QAAQ,aACzB,KAAC,KAAK,IAAC,OAAO,EAAC,OAAO,EAAC,KAAK,EAAE,KAAK,CAAC,IAAI,eAAa,KAAK,CAAC,IAAI,YAC5D,mBAAmB,CAAC,KAAK,CAAC,IAAI,CAAC,GAC1B,EACR,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,SAAS,EAAC,kBAAkB,YAKzC,qBAAqB,CAAC,KAAK,CAAC,GACxB,IACH,EACL,KAAK,CAAC,IAAI,KAAK,YAAY,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CACxD,KAAC,KAAK,IAAC,GAAG,EAAC,MAAM,YACd,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,CAC9B,KAAC,IAAI,IAEH,IAAI,EAAC,IAAI,EACT,IAAI,EAAC,OAAO;gCACZ,2DAA2D;gCAC3D,8DAA8D;gCAC9D,sDAAsD;gCACtD,KAAK,EAAE,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,YAE5E,qBAAqB,CAAC,KAAK,CAAC,IARxB,GAAG,KAAK,CAAC,IAAI,IAAI,CAAC,EAAE,CASpB,CACR,CAAC,GACI,CACT,CAAC,CAAC,CAAC,IAAI,IACF,EACP,SAAS,CAAC,CAAC,CAAC,CACX,KAAC,MAAM,IACL,IAAI,EAAC,SAAS,EACd,OAAO,EAAC,OAAO,gBACH,SAAS,CAAC,eAAe,CAAC,EACtC,OAAO,EAAE,SAAS,EAClB,SAAS,EAAC,UAAU,YAEpB,KAAC,CAAC,KAAG,GACE,CACV,CAAC,CAAC,CAAC,IAAI,IACJ,GACF,CACP,CAAA;AACH,CAAC","sourcesContent":["import { useEffect } from 'react'\nimport { CircleAlert, X } from 'lucide-react'\nimport type { OperationError } from '@elytracms/studio/lib/operations'\nimport { isSessionExpiredError } from '@elytracms/studio/lib/auth-errors'\nimport { reportSessionExpiredError } from '@elytracms/studio/lib/session-expiry'\nimport { Badge, Button, Row, Stack, Text } from '@elytracms/studio/ui'\nimport {\n operationErrorLabel,\n operationErrorMessage,\n operationIssueMessage,\n} from '@elytracms/studio/lib/persona/labels'\nimport { translate } from '@elytracms/studio/lib/i18n'\n\n/**\n * Shared error surface for failed/denied operations (EC-141). Every studio\n * surface renders rejected commands through this banner so a structured\n * `OperationError` — including its validation issues — is always *visible*,\n * never a silent failure.\n *\n * Session expiry (EC-177 gap 9, reworked by EC-403) is routed, not rendered:\n * an expired session is an app-level state, so the banner funnels the error\n * into the global session-expiry chokepoint (which raises the blocking\n * in-place re-auth overlay) and renders nothing itself — the per-surface\n * banner path remains only for non-expiry failures. The raw OIDC blob is\n * never shown either way.\n */\nexport function OperationErrorBanner({\n error,\n onDismiss,\n className,\n}: {\n error: OperationError\n onDismiss?: () => void\n className?: string\n}) {\n // Funnel every error into the EC-403 chokepoint (belt-and-braces for a\n // failure that reached this surface without passing the operations event\n // bus). Deduped per error instance there, so re-rendering a stale\n // persistError after re-auth never re-raises the overlay.\n useEffect(() => {\n reportSessionExpiredError(error)\n }, [error])\n\n if (isSessionExpiredError(error)) return null\n\n return (\n <div\n role=\"alert\"\n className={\n 'rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 ' + (className ?? '')\n }\n >\n <Row gap=\"2\" align=\"start\">\n <CircleAlert className=\"mt-0.5 size-4 shrink-0 text-destructive\" />\n <Stack gap=\"1\" className=\"min-w-0 flex-1\">\n <Row gap=\"2\" align=\"center\">\n <Badge variant=\"error\" title={error.code} data-code={error.code}>\n {operationErrorLabel(error.code)}\n </Badge>\n <Text size=\"sm\" className=\"text-destructive\">\n {/* EC-494: the envelope carries its own catalog ref across the wire,\n so this sentence localizes instead of sitting in English under a\n localized badge. Ref-less (a persistence-layer or hook-authored\n message) falls back to that English, visibly. */}\n {operationErrorMessage(error)}\n </Text>\n </Row>\n {error.code === 'validation' && error.issues.length > 0 ? (\n <Stack gap=\"none\">\n {error.issues.map((issue, i) => (\n <Text\n key={`${issue.code}-${i}`}\n size=\"xs\"\n tone=\"muted\"\n // The raw graph path (`pages.0.root.slots…`) is structural\n // machine addressing — kept inspectable on hover, never shown\n // as visible copy to a non-technical editor (EC-192).\n title={issue.path && issue.path.length > 0 ? issue.path.join('.') : undefined}\n >\n {operationIssueMessage(issue)}\n </Text>\n ))}\n </Stack>\n ) : null}\n </Stack>\n {onDismiss ? (\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n aria-label={translate('error.dismiss')}\n onClick={onDismiss}\n className=\"shrink-0\"\n >\n <X />\n </Button>\n ) : null}\n </Row>\n </div>\n )\n}\n"]}
|