@elytracms/studio 0.0.41 → 0.0.43
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/dist/components/assets/asset-detail.js +1 -1
- package/dist/components/assets/asset-detail.js.map +1 -1
- package/dist/components/assets/assets-module.js +19 -6
- package/dist/components/assets/assets-module.js.map +1 -1
- package/dist/components/assets/assets-store.d.ts +2 -1
- package/dist/components/assets/assets-store.js +4 -8
- package/dist/components/assets/assets-store.js.map +1 -1
- package/dist/components/assets/assets-view-preference.d.ts +0 -8
- package/dist/components/assets/assets-view-preference.js +12 -13
- package/dist/components/assets/assets-view-preference.js.map +1 -1
- package/dist/components/auth-context.d.ts +2 -1
- package/dist/components/auth-context.js +6 -8
- package/dist/components/auth-context.js.map +1 -1
- package/dist/components/builder/builder-store.d.ts +2 -2
- package/dist/components/builder/builder-store.js +4 -8
- package/dist/components/builder/builder-store.js.map +1 -1
- package/dist/components/builder/canvas.js +16 -2
- package/dist/components/builder/canvas.js.map +1 -1
- package/dist/components/cms/bulk-actions.js +2 -1
- package/dist/components/cms/bulk-actions.js.map +1 -1
- package/dist/components/cms/cell-edit.js +2 -1
- package/dist/components/cms/cell-edit.js.map +1 -1
- package/dist/components/cms/cms-composition-validators.d.ts +25 -0
- package/dist/components/cms/cms-composition-validators.js +75 -0
- package/dist/components/cms/cms-composition-validators.js.map +1 -0
- package/dist/components/cms/cms-labels.d.ts +15 -0
- package/dist/components/cms/cms-labels.js +27 -0
- package/dist/components/cms/cms-labels.js.map +1 -0
- package/dist/components/cms/cms-store.d.ts +23 -10
- package/dist/components/cms/cms-store.js +78 -154
- 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 +67 -67
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/details-pane.d.ts +11 -21
- package/dist/components/cms/details-pane.js +14 -31
- package/dist/components/cms/details-pane.js.map +1 -1
- package/dist/components/cms/discard-draft.d.ts +18 -0
- package/dist/components/cms/discard-draft.js +18 -0
- package/dist/components/cms/discard-draft.js.map +1 -1
- package/dist/components/cms/document-editor-context.d.ts +2 -1
- package/dist/components/cms/document-editor-context.js +4 -9
- package/dist/components/cms/document-editor-context.js.map +1 -1
- package/dist/components/cms/document-ref-utils.d.ts +33 -0
- package/dist/components/cms/document-ref-utils.js +47 -0
- package/dist/components/cms/document-ref-utils.js.map +1 -0
- package/dist/components/cms/document-url-path.d.ts +39 -0
- package/dist/components/cms/document-url-path.js +60 -0
- package/dist/components/cms/document-url-path.js.map +1 -0
- package/dist/components/cms/field-control.js +19 -13
- package/dist/components/cms/field-control.js.map +1 -1
- package/dist/components/cms/issues-pane.js +1 -1
- package/dist/components/cms/issues-pane.js.map +1 -1
- package/dist/components/cms/rich-text/block-commands.d.ts +36 -0
- package/dist/components/cms/rich-text/block-commands.js +47 -0
- package/dist/components/cms/rich-text/block-commands.js.map +1 -0
- package/dist/components/cms/rich-text/slash-command.js +25 -8
- package/dist/components/cms/rich-text/slash-command.js.map +1 -1
- package/dist/components/cms/rich-text/toolbar.js +3 -2
- package/dist/components/cms/rich-text/toolbar.js.map +1 -1
- package/dist/components/cms/version-compare.js +1 -1
- package/dist/components/cms/version-compare.js.map +1 -1
- package/dist/components/cms/versions-model.d.ts +31 -0
- package/dist/components/cms/versions-model.js +30 -0
- package/dist/components/cms/versions-model.js.map +1 -1
- package/dist/components/cms/view-bar.js +9 -6
- package/dist/components/cms/view-bar.js.map +1 -1
- package/dist/components/collab/collab-context.d.ts +2 -1
- package/dist/components/collab/collab-context.js +4 -8
- package/dist/components/collab/collab-context.js.map +1 -1
- package/dist/components/collection-view/collection-view-context.d.ts +9 -1
- package/dist/components/collection-view/collection-view-context.js +4 -9
- package/dist/components/collection-view/collection-view-context.js.map +1 -1
- package/dist/components/components/component-detail.js +2 -5
- package/dist/components/components/component-detail.js.map +1 -1
- package/dist/components/components/component-preview.js +8 -4
- package/dist/components/components/component-preview.js.map +1 -1
- package/dist/components/components/registry-browser.js +24 -5
- package/dist/components/components/registry-browser.js.map +1 -1
- package/dist/components/components/transfer-dialog.d.ts +10 -0
- package/dist/components/components/transfer-dialog.js +24 -8
- package/dist/components/components/transfer-dialog.js.map +1 -1
- package/dist/components/delivery-health-banner.d.ts +12 -6
- package/dist/components/delivery-health-banner.js +27 -29
- package/dist/components/delivery-health-banner.js.map +1 -1
- package/dist/components/dev-login-badge.d.ts +0 -2
- package/dist/components/dev-login-badge.js +1 -3
- package/dist/components/dev-login-badge.js.map +1 -1
- package/dist/components/inspector/inspector-sidebar.js +20 -3
- package/dist/components/inspector/inspector-sidebar.js.map +1 -1
- package/dist/components/playground-badge.d.ts +0 -2
- package/dist/components/playground-badge.js +0 -2
- package/dist/components/playground-badge.js.map +1 -1
- package/dist/components/project-list.js +2 -6
- package/dist/components/project-list.js.map +1 -1
- package/dist/components/project-session.d.ts +6 -2
- package/dist/components/project-session.js +32 -83
- package/dist/components/project-session.js.map +1 -1
- package/dist/components/project-switcher.js +2 -6
- package/dist/components/project-switcher.js.map +1 -1
- package/dist/components/redirects/redirects-store.d.ts +2 -1
- package/dist/components/redirects/redirects-store.js +4 -8
- package/dist/components/redirects/redirects-store.js.map +1 -1
- package/dist/components/studio-context.d.ts +2 -1
- package/dist/components/studio-context.js +4 -8
- package/dist/components/studio-context.js.map +1 -1
- package/dist/components/studio-shell.js +1 -1
- package/dist/components/studio-shell.js.map +1 -1
- package/dist/components/theme-context.d.ts +2 -1
- package/dist/components/theme-context.js +4 -8
- package/dist/components/theme-context.js.map +1 -1
- package/dist/components/use-hydration-phase.d.ts +65 -0
- package/dist/components/use-hydration-phase.js +64 -0
- package/dist/components/use-hydration-phase.js.map +1 -0
- package/dist/components/users/users-store.d.ts +2 -1
- package/dist/components/users/users-store.js +4 -8
- package/dist/components/users/users-store.js.map +1 -1
- package/dist/components/workspace-projects.d.ts +14 -0
- package/dist/components/workspace-projects.js +21 -0
- package/dist/components/workspace-projects.js.map +1 -1
- package/dist/lib/data/data-store.d.ts +2 -1
- package/dist/lib/data/data-store.js +4 -8
- package/dist/lib/data/data-store.js.map +1 -1
- package/dist/lib/delivery/instant-publish.d.ts +9 -13
- package/dist/lib/delivery/instant-publish.js +0 -7
- package/dist/lib/delivery/instant-publish.js.map +1 -1
- package/dist/lib/diff-view-preference.js +4 -4
- package/dist/lib/diff-view-preference.js.map +1 -1
- package/dist/lib/i18n/messages.en.js +7 -5
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/lib/local-preference.d.ts +37 -0
- package/dist/lib/local-preference.js +36 -0
- package/dist/lib/local-preference.js.map +1 -1
- package/dist/lib/persistence/convex-adapter.d.ts +3 -1
- package/dist/lib/persistence/convex-adapter.js +20 -0
- package/dist/lib/persistence/convex-adapter.js.map +1 -1
- package/dist/lib/rail.js +7 -6
- package/dist/lib/rail.js.map +1 -1
- package/dist/lib/required-context.d.ts +24 -0
- package/dist/lib/required-context.js +28 -0
- package/dist/lib/required-context.js.map +1 -0
- package/dist/lib/theme.js +11 -4
- package/dist/lib/theme.js.map +1 -1
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +1 -0
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/segmented-control.d.ts +46 -0
- package/dist/ui/segmented-control.js +13 -0
- package/dist/ui/segmented-control.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { OperationError, OperationResult } from '@elytracms/studio/lib/operations';
|
|
3
|
+
/**
|
|
4
|
+
* The shared hydration state machine (EC-329) behind both
|
|
5
|
+
* `ProjectSessionProvider` and `CmsSessionGate`: playground short-circuit,
|
|
6
|
+
* phase union, attempt-driven retry, cancelled-flag cleanup — a specific
|
|
7
|
+
* multi-part shape the two providers used to hand-roll identically. Real
|
|
8
|
+
* asymmetries between them stay explicit HOOK OPTIONS rather than being
|
|
9
|
+
* flattened away:
|
|
10
|
+
*
|
|
11
|
+
* - `ProjectSessionProvider`'s playground branch calls its `onResolved` side
|
|
12
|
+
* effect (`configureContentSession(null)`); `CmsSessionGate` passes no
|
|
13
|
+
* `onResolved` at all (it owns no such side effect — `loadCms`'s own body
|
|
14
|
+
* already calls it). `enabled` toggles whether `onResolved` fires in the
|
|
15
|
+
* playground fast-path — the ONE point of divergence between the two.
|
|
16
|
+
* - `onResolved` runs synchronously BEFORE the phase flips to `'ready'` (both
|
|
17
|
+
* the playground fast-path and the real load), so children reading a
|
|
18
|
+
* module global (e.g. `configureContentSession`) see it updated on the
|
|
19
|
+
* SAME render they first see `'ready'` — a bare `loader().then(setPhase)`
|
|
20
|
+
* cannot guarantee that ordering.
|
|
21
|
+
*/
|
|
22
|
+
export type HydrationPhase<T> = {
|
|
23
|
+
kind: 'loading';
|
|
24
|
+
} | {
|
|
25
|
+
kind: 'ready';
|
|
26
|
+
value: T;
|
|
27
|
+
} | {
|
|
28
|
+
kind: 'error';
|
|
29
|
+
error: OperationError;
|
|
30
|
+
};
|
|
31
|
+
export interface UseHydrationPhaseOptions<T> {
|
|
32
|
+
/** Playground (fixture) sessions resolve synchronously to `playgroundValue` — no loader call. */
|
|
33
|
+
playground: boolean;
|
|
34
|
+
/** The value `phase` resolves to in playground mode. */
|
|
35
|
+
playgroundValue: T;
|
|
36
|
+
/** Call `onResolved` for the playground fast-path too (default `false`). */
|
|
37
|
+
enabled?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Run synchronously before the phase flips to `'ready'` — in BOTH the
|
|
40
|
+
* playground fast-path (when `enabled`) and the real load. Read via a ref
|
|
41
|
+
* internally, so an inline (non-memoized) callback never re-triggers a fetch.
|
|
42
|
+
*/
|
|
43
|
+
onResolved?: (value: T) => void;
|
|
44
|
+
}
|
|
45
|
+
export interface UseHydrationPhaseResult<T> {
|
|
46
|
+
phase: HydrationPhase<T>;
|
|
47
|
+
/** Re-run the loader (the retry button's handler). */
|
|
48
|
+
retry: () => void;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* `loader`'s IDENTITY is the refetch trigger — memoize it (`useCallback`) over
|
|
52
|
+
* whatever inputs should re-hydrate (e.g. `projectId`), exactly like the
|
|
53
|
+
* original two effects keyed their dependency arrays.
|
|
54
|
+
*/
|
|
55
|
+
export declare function useHydrationPhase<T>(loader: () => Promise<OperationResult<T>>, options: UseHydrationPhaseOptions<T>): UseHydrationPhaseResult<T>;
|
|
56
|
+
/**
|
|
57
|
+
* Renders `phase`'s loading/error frames, or `children(value)` once ready —
|
|
58
|
+
* the identical 3-way branch both `ProjectSessionProvider` and
|
|
59
|
+
* `CmsSessionGate` used to repeat verbatim.
|
|
60
|
+
*/
|
|
61
|
+
export declare function HydrationBoundary<T>({ phase, retry, children, }: {
|
|
62
|
+
phase: HydrationPhase<T>;
|
|
63
|
+
retry: () => void;
|
|
64
|
+
children: (value: T) => ReactNode;
|
|
65
|
+
}): ReactNode;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { Button, Stack, Text } from '@elytracms/studio/ui';
|
|
4
|
+
import { translate } from '@elytracms/studio/lib/i18n';
|
|
5
|
+
import { OperationErrorBanner } from './operation-error';
|
|
6
|
+
/**
|
|
7
|
+
* `loader`'s IDENTITY is the refetch trigger — memoize it (`useCallback`) over
|
|
8
|
+
* whatever inputs should re-hydrate (e.g. `projectId`), exactly like the
|
|
9
|
+
* original two effects keyed their dependency arrays.
|
|
10
|
+
*/
|
|
11
|
+
export function useHydrationPhase(loader, options) {
|
|
12
|
+
const { playground, playgroundValue, enabled = false } = options;
|
|
13
|
+
const onResolvedRef = useRef(options.onResolved);
|
|
14
|
+
onResolvedRef.current = options.onResolved;
|
|
15
|
+
const [phase, setPhase] = useState(() => playground ? { kind: 'ready', value: playgroundValue } : { kind: 'loading' });
|
|
16
|
+
const [attempt, setAttempt] = useState(0);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (playground) {
|
|
19
|
+
if (enabled)
|
|
20
|
+
onResolvedRef.current?.(playgroundValue);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
let cancelled = false;
|
|
24
|
+
setPhase({ kind: 'loading' });
|
|
25
|
+
void loader().then((result) => {
|
|
26
|
+
if (cancelled)
|
|
27
|
+
return;
|
|
28
|
+
if (!result.ok) {
|
|
29
|
+
setPhase({ kind: 'error', error: result.error });
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
onResolvedRef.current?.(result.value);
|
|
33
|
+
setPhase({ kind: 'ready', value: result.value });
|
|
34
|
+
});
|
|
35
|
+
return () => {
|
|
36
|
+
cancelled = true;
|
|
37
|
+
};
|
|
38
|
+
// `onResolved` is read via the ref above (see the doc comment) — its
|
|
39
|
+
// identity intentionally does not retrigger this effect.
|
|
40
|
+
}, [loader, playground, attempt, enabled, playgroundValue]);
|
|
41
|
+
const retry = useCallback(() => setAttempt((n) => n + 1), []);
|
|
42
|
+
return { phase, retry };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Renders `phase`'s loading/error frames, or `children(value)` once ready —
|
|
46
|
+
* the identical 3-way branch both `ProjectSessionProvider` and
|
|
47
|
+
* `CmsSessionGate` used to repeat verbatim.
|
|
48
|
+
*/
|
|
49
|
+
export function HydrationBoundary({ phase, retry, children, }) {
|
|
50
|
+
if (phase.kind === 'loading')
|
|
51
|
+
return _jsx(SessionHydrating, {});
|
|
52
|
+
if (phase.kind === 'error')
|
|
53
|
+
return _jsx(SessionHydrationError, { error: phase.error, onRetry: retry });
|
|
54
|
+
return _jsx(_Fragment, { children: children(phase.value) });
|
|
55
|
+
}
|
|
56
|
+
/** Deterministic loading frame while persisted state is fetched (mirrors BackendGate). */
|
|
57
|
+
function SessionHydrating() {
|
|
58
|
+
return (_jsx("div", { className: "grid h-full min-h-64 flex-1 place-items-center p-8", "data-testid": "session-hydrating", children: _jsx(Text, { size: "sm", tone: "muted", children: translate('shell.session.loading') }) }));
|
|
59
|
+
}
|
|
60
|
+
/** A failed hydration is a visible, retryable state — never a fixture fallback. */
|
|
61
|
+
function SessionHydrationError({ error, onRetry, }) {
|
|
62
|
+
return (_jsx("div", { className: "grid h-full min-h-64 flex-1 place-items-center p-8", children: _jsxs(Stack, { gap: "3", className: "w-full max-w-xl", children: [_jsx(Text, { size: "sm", weight: "semibold", children: translate('shell.session.error.title') }), _jsx(OperationErrorBanner, { error: error }), _jsx(Text, { size: "xs", tone: "muted", children: translate('shell.session.error.body') }), _jsx(Button, { variant: "outline", size: "sm", onClick: onRetry, className: "self-start", children: translate('common.retry') })] }) }));
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=use-hydration-phase.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-hydration-phase.js","sourceRoot":"","sources":["../../src/components/use-hydration-phase.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { Button, Stack, Text } from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport type { OperationError, OperationResult } from '@elytracms/studio/lib/operations'\nimport { OperationErrorBanner } from './operation-error'\n\n/**\n * The shared hydration state machine (EC-329) behind both\n * `ProjectSessionProvider` and `CmsSessionGate`: playground short-circuit,\n * phase union, attempt-driven retry, cancelled-flag cleanup — a specific\n * multi-part shape the two providers used to hand-roll identically. Real\n * asymmetries between them stay explicit HOOK OPTIONS rather than being\n * flattened away:\n *\n * - `ProjectSessionProvider`'s playground branch calls its `onResolved` side\n * effect (`configureContentSession(null)`); `CmsSessionGate` passes no\n * `onResolved` at all (it owns no such side effect — `loadCms`'s own body\n * already calls it). `enabled` toggles whether `onResolved` fires in the\n * playground fast-path — the ONE point of divergence between the two.\n * - `onResolved` runs synchronously BEFORE the phase flips to `'ready'` (both\n * the playground fast-path and the real load), so children reading a\n * module global (e.g. `configureContentSession`) see it updated on the\n * SAME render they first see `'ready'` — a bare `loader().then(setPhase)`\n * cannot guarantee that ordering.\n */\nexport type HydrationPhase<T> =\n | { kind: 'loading' }\n | { kind: 'ready'; value: T }\n | { kind: 'error'; error: OperationError }\n\nexport interface UseHydrationPhaseOptions<T> {\n /** Playground (fixture) sessions resolve synchronously to `playgroundValue` — no loader call. */\n playground: boolean\n /** The value `phase` resolves to in playground mode. */\n playgroundValue: T\n /** Call `onResolved` for the playground fast-path too (default `false`). */\n enabled?: boolean\n /**\n * Run synchronously before the phase flips to `'ready'` — in BOTH the\n * playground fast-path (when `enabled`) and the real load. Read via a ref\n * internally, so an inline (non-memoized) callback never re-triggers a fetch.\n */\n onResolved?: (value: T) => void\n}\n\nexport interface UseHydrationPhaseResult<T> {\n phase: HydrationPhase<T>\n /** Re-run the loader (the retry button's handler). */\n retry: () => void\n}\n\n/**\n * `loader`'s IDENTITY is the refetch trigger — memoize it (`useCallback`) over\n * whatever inputs should re-hydrate (e.g. `projectId`), exactly like the\n * original two effects keyed their dependency arrays.\n */\nexport function useHydrationPhase<T>(\n loader: () => Promise<OperationResult<T>>,\n options: UseHydrationPhaseOptions<T>,\n): UseHydrationPhaseResult<T> {\n const { playground, playgroundValue, enabled = false } = options\n const onResolvedRef = useRef(options.onResolved)\n onResolvedRef.current = options.onResolved\n\n const [phase, setPhase] = useState<HydrationPhase<T>>(() =>\n playground ? { kind: 'ready', value: playgroundValue } : { kind: 'loading' },\n )\n const [attempt, setAttempt] = useState(0)\n\n useEffect(() => {\n if (playground) {\n if (enabled) onResolvedRef.current?.(playgroundValue)\n return\n }\n let cancelled = false\n setPhase({ kind: 'loading' })\n void loader().then((result) => {\n if (cancelled) return\n if (!result.ok) {\n setPhase({ kind: 'error', error: result.error })\n return\n }\n onResolvedRef.current?.(result.value)\n setPhase({ kind: 'ready', value: result.value })\n })\n return () => {\n cancelled = true\n }\n // `onResolved` is read via the ref above (see the doc comment) — its\n // identity intentionally does not retrigger this effect.\n }, [loader, playground, attempt, enabled, playgroundValue])\n\n const retry = useCallback(() => setAttempt((n) => n + 1), [])\n return { phase, retry }\n}\n\n/**\n * Renders `phase`'s loading/error frames, or `children(value)` once ready —\n * the identical 3-way branch both `ProjectSessionProvider` and\n * `CmsSessionGate` used to repeat verbatim.\n */\nexport function HydrationBoundary<T>({\n phase,\n retry,\n children,\n}: {\n phase: HydrationPhase<T>\n retry: () => void\n children: (value: T) => ReactNode\n}): ReactNode {\n if (phase.kind === 'loading') return <SessionHydrating />\n if (phase.kind === 'error') return <SessionHydrationError error={phase.error} onRetry={retry} />\n return <>{children(phase.value)}</>\n}\n\n/** Deterministic loading frame while persisted state is fetched (mirrors BackendGate). */\nfunction SessionHydrating() {\n return (\n <div className=\"grid h-full min-h-64 flex-1 place-items-center p-8\" data-testid=\"session-hydrating\">\n <Text size=\"sm\" tone=\"muted\">\n {translate('shell.session.loading')}\n </Text>\n </div>\n )\n}\n\n/** A failed hydration is a visible, retryable state — never a fixture fallback. */\nfunction SessionHydrationError({\n error,\n onRetry,\n}: {\n error: OperationError\n onRetry: () => void\n}) {\n return (\n <div className=\"grid h-full min-h-64 flex-1 place-items-center p-8\">\n <Stack gap=\"3\" className=\"w-full max-w-xl\">\n <Text size=\"sm\" weight=\"semibold\">\n {translate('shell.session.error.title')}\n </Text>\n <OperationErrorBanner error={error} />\n <Text size=\"xs\" tone=\"muted\">\n {translate('shell.session.error.body')}\n </Text>\n <Button variant=\"outline\" size=\"sm\" onClick={onRetry} className=\"self-start\">\n {translate('common.retry')}\n </Button>\n </Stack>\n </div>\n )\n}\n"]}
|
|
@@ -33,8 +33,9 @@ export interface UsersStore {
|
|
|
33
33
|
/** Re-list from the backend (the public escape hatch). */
|
|
34
34
|
refresh(): Promise<void>;
|
|
35
35
|
}
|
|
36
|
+
declare const useUsers: () => UsersStore;
|
|
36
37
|
export declare function UsersProvider({ projectId, children, }: {
|
|
37
38
|
projectId: string;
|
|
38
39
|
children: ReactNode;
|
|
39
40
|
}): import("react").JSX.Element;
|
|
40
|
-
export
|
|
41
|
+
export { useUsers };
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { useCallback, useMemo } from 'react';
|
|
3
3
|
import { getStudioOperations } from '@elytracms/studio/lib/operations';
|
|
4
4
|
import { listStoreState, useOperationCache, useOperationQuery } from '@elytracms/studio/lib/query';
|
|
5
|
-
|
|
5
|
+
import { createRequiredContext } from '@elytracms/studio/lib/required-context';
|
|
6
|
+
const { Context: UsersContext, useRequired: useUsers } = createRequiredContext('Users');
|
|
6
7
|
const EMPTY_USERS = [];
|
|
7
8
|
/** Cache key for a project's users rail (shared by the query and its patches). */
|
|
8
9
|
function usersQueryKey(projectId) {
|
|
@@ -73,10 +74,5 @@ export function UsersProvider({ projectId, children, }) {
|
|
|
73
74
|
}), [projectId, status, errorMessage, users, save, remove, refresh]);
|
|
74
75
|
return _jsx(UsersContext.Provider, { value: store, children: children });
|
|
75
76
|
}
|
|
76
|
-
export
|
|
77
|
-
const store = useContext(UsersContext);
|
|
78
|
-
if (!store)
|
|
79
|
-
throw new Error('useUsers must be used within a <UsersProvider>.');
|
|
80
|
-
return store;
|
|
81
|
-
}
|
|
77
|
+
export { useUsers };
|
|
82
78
|
//# sourceMappingURL=users-store.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"users-store.js","sourceRoot":"","sources":["../../../src/components/users/users-store.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"users-store.js","sourceRoot":"","sources":["../../../src/components/users/users-store.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useMemo } from 'react'\nimport type { ReactNode } from 'react'\nimport type { ProjectRole, SaveUserInput, UserRecord } from '@elytracms/core/persistence'\nimport { getStudioOperations } from '@elytracms/studio/lib/operations'\nimport type { OperationsClient } from '@elytracms/studio/lib/operations'\nimport { listStoreState, useOperationCache, useOperationQuery } from '@elytracms/studio/lib/query'\nimport { createRequiredContext } from '@elytracms/studio/lib/required-context'\n\n/**\n * Studio users store (EC-240, 240b). The users rail — role + custom fields\n * joined to auth identity — read + written through the shared operations client\n * (`users.*` commands: validate → authorize → mutate → event), the same seam the\n * assets store uses. Admin-only: every `users.*` operation is gated at the\n * chokepoint AND server-side, so a non-admin session sees an authorization error\n * rather than data. Mirrors `assets-store.tsx`, minus the blob/upload machinery\n * (users have no bytes).\n */\n\nexport interface UsersStore {\n /** The active project (deployment) whose members this rail manages. */\n projectId: string\n status: 'loading' | 'ready' | 'error'\n errorMessage?: string\n users: readonly UserRecord[]\n /** Grant/modify a user's role(s) + custom fields (admin-only). */\n save(input: SaveUserInput): Promise<{ ok: true; user: UserRecord } | { ok: false; message: string }>\n /** Revoke a user's studio membership (does not delete the auth identity). */\n remove(userId: string): Promise<{ ok: true } | { ok: false; message: string }>\n /** Re-list from the backend (the public escape hatch). */\n refresh(): Promise<void>\n}\n\nconst { Context: UsersContext, useRequired: useUsers } = createRequiredContext<UsersStore>('Users')\n\nconst EMPTY_USERS: readonly UserRecord[] = []\n\n/** Cache key for a project's users rail (shared by the query and its patches). */\nfunction usersQueryKey(projectId: string): readonly [string, string] {\n return ['users', projectId]\n}\n\n/** Load a project's studio users (pure over the operations client). A list\n * failure throws so the query surfaces it as an error status. */\nasync function loadUsers(operations: OperationsClient, projectId: string): Promise<UserRecord[]> {\n const result = await operations.users.list({ projectId })\n if (!result.ok) throw new Error(result.error.message)\n return [...result.value]\n}\n\nexport function UsersProvider({\n projectId,\n children,\n}: {\n projectId: string\n children: ReactNode\n}) {\n // The users list, read through the one operations-backed query cache\n // (EC-308/AD-17). This provider is the sole writer of its project's users, so\n // its own mutations patch the cache in place rather than re-listing.\n const query = useOperationQuery<UserRecord[]>({\n queryKey: usersQueryKey(projectId),\n loader: (operations) => loadUsers(operations, projectId),\n })\n const cache = useOperationCache()\n const refetch = query.refetch\n const users = query.data ?? EMPTY_USERS\n const { status, errorMessage } = listStoreState(query)\n\n const refresh = useCallback(async () => {\n await refetch()\n }, [refetch])\n\n const save = useCallback<UsersStore['save']>(\n async (input) => {\n // Writes go through the `users.save` command via the shared operations\n // client (EC-141) — validate → authorize (admin) → mutate → event.\n const { operations } = await getStudioOperations()\n const result = await operations.users.save({\n projectId,\n userId: input.id,\n // 240a bridge: the contract's `roles: string[]` is permissive (240c-ready);\n // the operation input constrains it to the current enum + re-validates at\n // runtime. The cast crosses that permissive→strict seam.\n roles: input.roles as ProjectRole[],\n ...(input.meta !== undefined ? { meta: input.meta } : {}),\n })\n if (!result.ok) return { ok: false, message: result.error.message }\n // Patch the one updated record into the cache rather than re-listing.\n const saved = result.value\n cache.patch<UserRecord[]>(usersQueryKey(projectId), (prev) => {\n const rest = (prev ?? []).filter((u) => u.id !== saved.id)\n return [...rest, saved].sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))\n })\n return { ok: true, user: saved }\n },\n [projectId, cache],\n )\n\n const remove = useCallback<UsersStore['remove']>(\n async (userId) => {\n const { operations } = await getStudioOperations()\n const result = await operations.users.remove({ projectId, userId })\n if (!result.ok) return { ok: false, message: result.error.message }\n cache.patch<UserRecord[]>(usersQueryKey(projectId), (prev) =>\n (prev ?? []).filter((u) => u.id !== userId),\n )\n return { ok: true }\n },\n [projectId, cache],\n )\n\n const store = useMemo<UsersStore>(\n () => ({\n projectId,\n status,\n ...(errorMessage !== undefined ? { errorMessage } : {}),\n users,\n save,\n remove,\n refresh,\n }),\n [projectId, status, errorMessage, users, save, remove, refresh],\n )\n\n return <UsersContext.Provider value={store}>{children}</UsersContext.Provider>\n}\n\nexport { useUsers }\n"]}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Project } from '@elytracms/core/studio-core';
|
|
1
2
|
import type { SwitcherEntry, WorkspaceState } from '@elytracms/studio/lib/workspace';
|
|
2
3
|
/**
|
|
3
4
|
* Config-driven project source for the switcher + launcher (EC-154, reframed for
|
|
@@ -11,3 +12,16 @@ export declare function useSwitcherEntries(): {
|
|
|
11
12
|
entries: SwitcherEntry[];
|
|
12
13
|
workspace: WorkspaceState;
|
|
13
14
|
};
|
|
15
|
+
/**
|
|
16
|
+
* The switcher/launcher's shared "recent projects" read model (EC-329):
|
|
17
|
+
* `studio.context.recentProjectIds` → resolved `Project`s → filtered to
|
|
18
|
+
* entries still visible in `entries` (the caller's config-driven switcher
|
|
19
|
+
* entries — a recent project retired from the config must not still show)
|
|
20
|
+
* → capped at {@link RECENT_PROJECTS_LIMIT}. `project-switcher.tsx` passes
|
|
21
|
+
* `exclude` to additionally drop the active project (already shown as
|
|
22
|
+
* current); `project-list.tsx`'s recents rail sits alongside the full grid,
|
|
23
|
+
* so it doesn't exclude anything.
|
|
24
|
+
*/
|
|
25
|
+
export declare function useRecentProjects(entries: readonly SwitcherEntry[], options?: {
|
|
26
|
+
exclude?: string;
|
|
27
|
+
}): Project[];
|
|
@@ -15,4 +15,25 @@ export function useSwitcherEntries() {
|
|
|
15
15
|
const entries = useMemo(() => selectSwitcherProjects({ workspace, persisted: studio.projects }), [workspace, studio.projects]);
|
|
16
16
|
return { entries, workspace };
|
|
17
17
|
}
|
|
18
|
+
/** How many recent projects the switcher/launcher rails show. */
|
|
19
|
+
const RECENT_PROJECTS_LIMIT = 4;
|
|
20
|
+
/**
|
|
21
|
+
* The switcher/launcher's shared "recent projects" read model (EC-329):
|
|
22
|
+
* `studio.context.recentProjectIds` → resolved `Project`s → filtered to
|
|
23
|
+
* entries still visible in `entries` (the caller's config-driven switcher
|
|
24
|
+
* entries — a recent project retired from the config must not still show)
|
|
25
|
+
* → capped at {@link RECENT_PROJECTS_LIMIT}. `project-switcher.tsx` passes
|
|
26
|
+
* `exclude` to additionally drop the active project (already shown as
|
|
27
|
+
* current); `project-list.tsx`'s recents rail sits alongside the full grid,
|
|
28
|
+
* so it doesn't exclude anything.
|
|
29
|
+
*/
|
|
30
|
+
export function useRecentProjects(entries, options) {
|
|
31
|
+
const studio = useStudio();
|
|
32
|
+
const exclude = options?.exclude;
|
|
33
|
+
return useMemo(() => studio.context.recentProjectIds
|
|
34
|
+
.map((id) => studio.getProject(id))
|
|
35
|
+
.filter((p) => p != null && p.id !== exclude)
|
|
36
|
+
.filter((p) => entries.some((entry) => entry.kind === 'project' && entry.project.id === p.id))
|
|
37
|
+
.slice(0, RECENT_PROJECTS_LIMIT), [studio, entries, exclude]);
|
|
38
|
+
}
|
|
18
39
|
//# sourceMappingURL=workspace-projects.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace-projects.js","sourceRoot":"","sources":["../../src/components/workspace-projects.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"workspace-projects.js","sourceRoot":"","sources":["../../src/components/workspace-projects.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAE/B,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAA;AAE3F,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAE5C;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB;IAChC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAC1B,MAAM,SAAS,GAAG,iBAAiB,EAAE,CAAA;IACrC,MAAM,OAAO,GAAG,OAAO,CACrB,GAAG,EAAE,CAAC,sBAAsB,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,EACvE,CAAC,SAAS,EAAE,MAAM,CAAC,QAAQ,CAAC,CAC7B,CAAA;IACD,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,CAAA;AAC/B,CAAC;AAED,iEAAiE;AACjE,MAAM,qBAAqB,GAAG,CAAC,CAAA;AAE/B;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAC/B,OAAiC,EACjC,OAA8B;IAE9B,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAC1B,MAAM,OAAO,GAAG,OAAO,EAAE,OAAO,CAAA;IAChC,OAAO,OAAO,CACZ,GAAG,EAAE,CACH,MAAM,CAAC,OAAO,CAAC,gBAAgB;SAC5B,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;SAClC,MAAM,CAAC,CAAC,CAAC,EAAgB,EAAE,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,EAAE,KAAK,OAAO,CAAC;SAC1D,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;SAC7F,KAAK,CAAC,CAAC,EAAE,qBAAqB,CAAC,EACpC,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAC3B,CAAA;AACH,CAAC","sourcesContent":["import { useMemo } from 'react'\nimport type { Project } from '@elytracms/core/studio-core'\nimport { getWorkspaceState, selectSwitcherProjects } from '@elytracms/studio/lib/workspace'\nimport type { SwitcherEntry, WorkspaceState } from '@elytracms/studio/lib/workspace'\nimport { useStudio } from './studio-context'\n\n/**\n * Config-driven project source for the switcher + launcher (EC-154, reframed for\n * EC-179). The decision logic is the pure `selectSwitcherProjects`; this hook\n * wires it to the studio store (config-derived read model) and the workspace\n * state. Visibility is config-driven and NOT membership-filtered — one project\n * per backend, so access is enforced per-backend on entry (sign-in + role hook),\n * never by hiding a project you would authenticate into.\n */\nexport function useSwitcherEntries(): { entries: SwitcherEntry[]; workspace: WorkspaceState } {\n const studio = useStudio()\n const workspace = getWorkspaceState()\n const entries = useMemo(\n () => selectSwitcherProjects({ workspace, persisted: studio.projects }),\n [workspace, studio.projects],\n )\n return { entries, workspace }\n}\n\n/** How many recent projects the switcher/launcher rails show. */\nconst RECENT_PROJECTS_LIMIT = 4\n\n/**\n * The switcher/launcher's shared \"recent projects\" read model (EC-329):\n * `studio.context.recentProjectIds` → resolved `Project`s → filtered to\n * entries still visible in `entries` (the caller's config-driven switcher\n * entries — a recent project retired from the config must not still show)\n * → capped at {@link RECENT_PROJECTS_LIMIT}. `project-switcher.tsx` passes\n * `exclude` to additionally drop the active project (already shown as\n * current); `project-list.tsx`'s recents rail sits alongside the full grid,\n * so it doesn't exclude anything.\n */\nexport function useRecentProjects(\n entries: readonly SwitcherEntry[],\n options?: { exclude?: string },\n): Project[] {\n const studio = useStudio()\n const exclude = options?.exclude\n return useMemo(\n () =>\n studio.context.recentProjectIds\n .map((id) => studio.getProject(id))\n .filter((p): p is Project => p != null && p.id !== exclude)\n .filter((p) => entries.some((entry) => entry.kind === 'project' && entry.project.id === p.id))\n .slice(0, RECENT_PROJECTS_LIMIT),\n [studio, entries, exclude],\n )\n}\n"]}
|
|
@@ -78,6 +78,7 @@ export type DataAction = {
|
|
|
78
78
|
* live here and the app's test harness has no DOM to drive effects through.
|
|
79
79
|
*/
|
|
80
80
|
export declare function dataStateReducer(state: DataState, action: DataAction): DataState;
|
|
81
|
+
declare const useData: () => DataStore;
|
|
81
82
|
/**
|
|
82
83
|
* The session's starting state: EMPTY. The studio seeds no sample sources (Oli,
|
|
83
84
|
* 2026-06-19) — real sources arrive from the project's config collections (the
|
|
@@ -94,4 +95,4 @@ export interface DataProviderProps {
|
|
|
94
95
|
projectId?: string;
|
|
95
96
|
}
|
|
96
97
|
export declare function DataProvider({ children, projectId }: DataProviderProps): import("react").JSX.Element;
|
|
97
|
-
export
|
|
98
|
+
export { useData };
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { useCallback, useEffect, useMemo, useReducer, useState } from 'react';
|
|
3
3
|
import { SourceRegistry, captureSample, inferSchema, resolveBinding, resolveCondition, tokensForSchema, } from '@elytracms/core/data-binding';
|
|
4
4
|
import { getStudioOperations } from '../operations';
|
|
5
5
|
import { useOperationCache, useOperationQuery } from '@elytracms/studio/lib/query';
|
|
6
|
+
import { createRequiredContext } from '@elytracms/studio/lib/required-context';
|
|
6
7
|
import { STUDIO_SYSTEM_COLLECTION_IDS } from '../operations/session-hydration';
|
|
7
8
|
import { resolveDeploymentCollections } from '../workspace';
|
|
8
9
|
import { hasContentSession } from './collection-content';
|
|
@@ -67,7 +68,7 @@ export function dataStateReducer(state, action) {
|
|
|
67
68
|
return state;
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
|
-
const DataContext =
|
|
71
|
+
const { Context: DataContext, useRequired: useData } = createRequiredContext('Data');
|
|
71
72
|
/**
|
|
72
73
|
* The session's starting state: EMPTY. The studio seeds no sample sources (Oli,
|
|
73
74
|
* 2026-06-19) — real sources arrive from the project's config collections (the
|
|
@@ -206,10 +207,5 @@ export function DataProvider({ children, projectId }) {
|
|
|
206
207
|
}, [state, persist, persistError]);
|
|
207
208
|
return _jsx(DataContext.Provider, { value: store, children: children });
|
|
208
209
|
}
|
|
209
|
-
export
|
|
210
|
-
const store = useContext(DataContext);
|
|
211
|
-
if (!store)
|
|
212
|
-
throw new Error('useData must be used within a <DataProvider>.');
|
|
213
|
-
return store;
|
|
214
|
-
}
|
|
210
|
+
export { useData };
|
|
215
211
|
//# sourceMappingURL=data-store.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-store.js","sourceRoot":"","sources":["../../../src/lib/data/data-store.tsx"],"names":[],"mappings":"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","sourcesContent":["import { createContext, useCallback, useContext, useEffect, useMemo, useReducer, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport {\n SourceRegistry,\n captureSample,\n inferSchema,\n resolveBinding,\n resolveCondition,\n tokensForSchema,\n} from '@elytracms/core/data-binding'\nimport type {\n BindingResolution,\n ConditionResolution,\n DataSource,\n InferredSchema,\n PayloadStore,\n RepeaterItemContext,\n} from '@elytracms/core/data-binding'\nimport type { BindingReference, Condition, JsonValue } from '@elytracms/core/project-graph'\nimport { getStudioOperations } from '../operations'\nimport type { OperationResult, OperationsClient } from '../operations'\nimport { useOperationCache, useOperationQuery } from '@elytracms/studio/lib/query'\nimport { STUDIO_SYSTEM_COLLECTION_IDS } from '../operations/session-hydration'\nimport { resolveDeploymentCollections } from '../workspace'\nimport { hasContentSession } from './collection-content'\nimport { captureSourceSample, cmsSourceForCollection } from './sample-capture'\nimport { deleteDataSource, loadDataSources, saveDataSource } from './sources-store'\n\n/**\n * The project's live data layer (brief §4.6, §7.5, EC-167). Holds the source\n * registry the Builder's Content tab binds through, plus the captured payloads\n * that drive binding resolution. One uniform model across CMS queries, data\n * providers, and the built-in REST/Convex kinds — resolution, sampling,\n * inference and tokenization treat them all identically.\n *\n * There is no source-authoring UI (EC-167): sources exist because code declared\n * them (registered data providers), the schema implied them (per-collection CMS\n * queries), or an editor flow provisioned them (repeating sections, template\n * pages).\n *\n * Persistence: when mounted with a `projectId`, the store hydrates the\n * project's persisted source records (the `cms-data-sources` system collection,\n * see `sources-store.ts`) over the deterministic playground seeds — persisted\n * wins by id, but never over a source provisioned/removed in THIS session —\n * and every provision/remove writes through `@elytracms/core/operations`. Without a\n * `projectId` (tests, fixtures) the store is pure session state, exactly as\n * before.\n */\nexport interface DataStore {\n sources: DataSource[]\n registry: SourceRegistry\n payloads: PayloadStore\n knownSourceIds: ReadonlySet<string>\n\n getSource: (id: string) => DataSource | undefined\n removeSource: (id: string) => void\n /**\n * Create or replace a source with a known id + config and capture its sample\n * immediately (EC-166/EC-167): repeating sections and template pages\n * provision their per-scope CMS query / provider sources through this.\n */\n provisionSource: (source: DataSource) => void\n\n /**\n * The latest persistence failure in plain language, or `null`. Persistence\n * is best-effort on top of the live session (the canvas keeps working on\n * session state), but a failure is recorded here — visible, never silent.\n */\n persistError: string | null\n\n /** The stable path tokens a source exposes from its inferred schema (EC-085). */\n tokensOf: (id: string) => string[]\n schemaOf: (id: string) => InferredSchema | undefined\n sampleOf: (id: string) => JsonValue | undefined\n\n /** Resolve a binding to a renderer-ready resolution (value/props/condition/issue). */\n resolve: (ref: BindingReference, item?: RepeaterItemContext) => BindingResolution\n /** Resolve a binding to the concrete value the renderer injects (EC-086–089). */\n resolveValue: (ref: BindingReference, item?: RepeaterItemContext) => JsonValue | undefined\n /** Evaluate a condition against the captured payloads (EC-088). */\n resolveCondition: (condition: Condition, item?: RepeaterItemContext) => ConditionResolution\n}\n\nexport interface DataState {\n sources: DataSource[]\n /**\n * Ids provisioned or removed in THIS session. Hydration (which resolves\n * asynchronously) never overrides them — the editor's latest action wins\n * over whatever was persisted before it.\n */\n sessionIds: ReadonlySet<string>\n}\n\nexport type DataAction =\n | { type: 'remove'; id: string }\n | { type: 'provision'; source: DataSource }\n /** Merge the project's persisted source records in (persisted wins over seeds). */\n | { type: 'hydrate'; sources: DataSource[] }\n\nfunction upsert(sources: DataSource[], source: DataSource): DataSource[] {\n const exists = sources.some((s) => s.id === source.id)\n return exists ? sources.map((s) => (s.id === source.id ? source : s)) : [...sources, source]\n}\n\n/**\n * A persisted record hydrates as-is when it carries its captured sample;\n * otherwise recapture. Exception (EC-177 gap 1): with a hydrated content\n * session configured (Convex mode), CMS sources ALWAYS recapture by replaying\n * their stored query over the live documents — a sample captured by an earlier\n * session may carry stale (or fixture-derived) entries.\n */\nfunction withSample(source: DataSource): DataSource {\n if (source.kind === 'cms' && hasContentSession()) {\n const sample = captureSourceSample(source)\n return sample !== undefined ? captureSample(source, sample) : source\n }\n if (source.sample !== undefined) return source\n // No persisted sample: try an honest capture (a provider runs; anything the\n // studio can't fetch returns undefined) — never fabricate.\n const sample = captureSourceSample(source)\n return sample !== undefined ? captureSample(source, sample) : source\n}\n\n/**\n * The store's reducer, exported because hydration/session-precedence semantics\n * live here and the app's test harness has no DOM to drive effects through.\n */\nexport function dataStateReducer(state: DataState, action: DataAction): DataState {\n switch (action.type) {\n case 'remove':\n return {\n ...state,\n sources: state.sources.filter((s) => s.id !== action.id),\n sessionIds: new Set([...state.sessionIds, action.id]),\n }\n case 'provision': {\n // Upsert by id; attach a sample only if one can be captured honestly (a\n // provider run, or live CMS content) — otherwise the source carries none.\n const sample = captureSourceSample(action.source)\n const provisioned = sample !== undefined ? captureSample(action.source, sample) : action.source\n return {\n ...state,\n sources: upsert(state.sources, provisioned),\n sessionIds: new Set([...state.sessionIds, provisioned.id]),\n }\n }\n case 'hydrate': {\n let sources = state.sources\n for (const source of action.sources) {\n if (state.sessionIds.has(source.id)) continue // this session already decided\n sources = upsert(sources, withSample(source))\n }\n return sources === state.sources ? state : { ...state, sources }\n }\n default:\n return state\n }\n}\n\nconst DataContext = createContext<DataStore | null>(null)\n\n/**\n * The session's starting state: EMPTY. The studio seeds no sample sources (Oli,\n * 2026-06-19) — real sources arrive from the project's config collections (the\n * hydrate effect, Convex mode) or are added by the author. Nothing is faked.\n */\nexport function initialDataState(): DataState {\n return { sources: [], sessionIds: new Set() }\n}\n\nexport interface DataProviderProps {\n children: ReactNode\n /**\n * When set, the store hydrates from and persists to this project's\n * `cms-data-sources` system collection (through `@elytracms/core/operations`).\n * Without it the store is pure session state (tests, fixtures).\n */\n projectId?: string\n}\n\nexport function DataProvider({ children, projectId }: DataProviderProps) {\n const [state, dispatch] = useReducer(dataStateReducer, undefined, initialDataState)\n const [persistError, setPersistError] = useState<string | null>(null)\n\n // EC-192: surface every config-driven content collection as a pickable CMS\n // source, so the binding/condition pickers list what actually exists — the\n // same collection set the CMS uses (`resolveDeploymentCollections`) — rather\n // than only the demo fixture sources. Merged additively (the `hydrate` upsert\n // skips session-decided ids and recaptures live samples in Convex mode); studio\n // SYSTEM collections (routes/redirects/saved views/data sources) are excluded.\n useEffect(() => {\n if (projectId === undefined) return\n const collections = resolveDeploymentCollections().filter(\n (collection) => !STUDIO_SYSTEM_COLLECTION_IDS.has(collection.id),\n )\n if (collections.length === 0) return\n dispatch({ type: 'hydrate', sources: collections.map(cmsSourceForCollection) })\n }, [projectId])\n\n // Hydrate the project's persisted sources through the one operations-backed\n // query cache (EC-308/AD-17). Sources provisioned by other sessions on the\n // same project show up here; the playground seeds stay for everything not\n // persisted. The reducer's `hydrate` keeps session precedence — a source\n // provisioned/removed in THIS session always wins over the hydrated list.\n const cache = useOperationCache()\n const sourcesQuery = useOperationQuery<DataSource[]>({\n queryKey: ['dataSources', projectId ?? '∅'],\n loader: async (operations) => {\n const result = await loadDataSources(operations, projectId as string)\n if (!result.ok) throw new Error(result.error.message)\n return result.value\n },\n enabled: projectId !== undefined,\n })\n const hydratedSources = sourcesQuery.data\n useEffect(() => {\n if (hydratedSources) dispatch({ type: 'hydrate', sources: hydratedSources })\n }, [hydratedSources])\n const sourcesError = sourcesQuery.isError ? sourcesQuery.error : null\n useEffect(() => {\n if (sourcesError)\n setPersistError(sourcesError instanceof Error ? sourcesError.message : 'Loading data sources failed.')\n }, [sourcesError])\n\n // Best-effort persistence on top of the live session: the canvas keeps\n // working on session state; a failed write surfaces via `persistError`. On a\n // confirmed write, invalidate the hydration query so a later remount\n // re-hydrates the provisioned/removed source rather than a stale list.\n const persist = useCallback(\n (write: (operations: OperationsClient, projectId: string) => Promise<OperationResult<void>>) => {\n if (projectId === undefined) return\n void getStudioOperations()\n .then(({ operations }) => write(operations, projectId))\n .then((result) => {\n if (!result.ok) {\n setPersistError(result.error.message)\n return\n }\n void cache.invalidate(['dataSources', projectId])\n })\n .catch((error: unknown) => {\n setPersistError(error instanceof Error ? error.message : 'Saving the data source failed.')\n })\n },\n [projectId, cache],\n )\n\n const store = useMemo<DataStore>(() => {\n const registry = SourceRegistry.from(state.sources)\n const payloads: Record<string, JsonValue> = {}\n for (const source of state.sources) {\n if (source.sample !== undefined) payloads[source.id] = source.sample\n }\n const knownSourceIds = registry.knownSourceIds()\n const ctx = { payloads, registry }\n\n const getSource = (id: string) => registry.get(id)\n const resolve = (ref: BindingReference, item?: RepeaterItemContext) =>\n resolveBinding(ref, ctx, item)\n\n return {\n sources: state.sources,\n registry,\n payloads,\n knownSourceIds,\n getSource,\n removeSource: (id) => {\n dispatch({ type: 'remove', id })\n persist((operations, pid) => deleteDataSource(operations, pid, id))\n },\n provisionSource: (source) => {\n dispatch({ type: 'provision', source })\n // Persist exactly what the session resolves: the source plus any sample\n // that can be captured honestly (a provider run / live CMS content) —\n // never a fabricated one. Matches the `provision` reducer.\n const sample = captureSourceSample(source)\n const provisioned = sample !== undefined ? captureSample(source, sample) : source\n persist((operations, pid) => saveDataSource(operations, pid, provisioned))\n },\n persistError,\n tokensOf: (id) => {\n const schema = getSource(id)?.schema\n return schema ? tokensForSchema(schema) : []\n },\n schemaOf: (id) => {\n const source = getSource(id)\n if (source?.schema) return source.schema\n if (source?.sample !== undefined) return inferSchema(source.sample)\n return undefined\n },\n sampleOf: (id) => getSource(id)?.sample,\n resolve,\n resolveValue: (ref, item) => {\n const r = resolve(ref, item)\n switch (r.kind) {\n case 'value':\n return r.value\n case 'props':\n return r.props\n case 'condition':\n return r.pass\n case 'issue':\n return undefined\n default: {\n const never: never = r\n return never\n }\n }\n },\n resolveCondition: (condition, item) => resolveCondition(condition, ctx, item),\n }\n }, [state, persist, persistError])\n\n return <DataContext.Provider value={store}>{children}</DataContext.Provider>\n}\n\nexport function useData(): DataStore {\n const store = useContext(DataContext)\n if (!store) throw new Error('useData must be used within a <DataProvider>.')\n return store\n}\n"]}
|
|
1
|
+
{"version":3,"file":"data-store.js","sourceRoot":"","sources":["../../../src/lib/data/data-store.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useMemo, useReducer, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport {\n SourceRegistry,\n captureSample,\n inferSchema,\n resolveBinding,\n resolveCondition,\n tokensForSchema,\n} from '@elytracms/core/data-binding'\nimport type {\n BindingResolution,\n ConditionResolution,\n DataSource,\n InferredSchema,\n PayloadStore,\n RepeaterItemContext,\n} from '@elytracms/core/data-binding'\nimport type { BindingReference, Condition, JsonValue } from '@elytracms/core/project-graph'\nimport { getStudioOperations } from '../operations'\nimport type { OperationResult, OperationsClient } from '../operations'\nimport { useOperationCache, useOperationQuery } from '@elytracms/studio/lib/query'\nimport { createRequiredContext } from '@elytracms/studio/lib/required-context'\nimport { STUDIO_SYSTEM_COLLECTION_IDS } from '../operations/session-hydration'\nimport { resolveDeploymentCollections } from '../workspace'\nimport { hasContentSession } from './collection-content'\nimport { captureSourceSample, cmsSourceForCollection } from './sample-capture'\nimport { deleteDataSource, loadDataSources, saveDataSource } from './sources-store'\n\n/**\n * The project's live data layer (brief §4.6, §7.5, EC-167). Holds the source\n * registry the Builder's Content tab binds through, plus the captured payloads\n * that drive binding resolution. One uniform model across CMS queries, data\n * providers, and the built-in REST/Convex kinds — resolution, sampling,\n * inference and tokenization treat them all identically.\n *\n * There is no source-authoring UI (EC-167): sources exist because code declared\n * them (registered data providers), the schema implied them (per-collection CMS\n * queries), or an editor flow provisioned them (repeating sections, template\n * pages).\n *\n * Persistence: when mounted with a `projectId`, the store hydrates the\n * project's persisted source records (the `cms-data-sources` system collection,\n * see `sources-store.ts`) over the deterministic playground seeds — persisted\n * wins by id, but never over a source provisioned/removed in THIS session —\n * and every provision/remove writes through `@elytracms/core/operations`. Without a\n * `projectId` (tests, fixtures) the store is pure session state, exactly as\n * before.\n */\nexport interface DataStore {\n sources: DataSource[]\n registry: SourceRegistry\n payloads: PayloadStore\n knownSourceIds: ReadonlySet<string>\n\n getSource: (id: string) => DataSource | undefined\n removeSource: (id: string) => void\n /**\n * Create or replace a source with a known id + config and capture its sample\n * immediately (EC-166/EC-167): repeating sections and template pages\n * provision their per-scope CMS query / provider sources through this.\n */\n provisionSource: (source: DataSource) => void\n\n /**\n * The latest persistence failure in plain language, or `null`. Persistence\n * is best-effort on top of the live session (the canvas keeps working on\n * session state), but a failure is recorded here — visible, never silent.\n */\n persistError: string | null\n\n /** The stable path tokens a source exposes from its inferred schema (EC-085). */\n tokensOf: (id: string) => string[]\n schemaOf: (id: string) => InferredSchema | undefined\n sampleOf: (id: string) => JsonValue | undefined\n\n /** Resolve a binding to a renderer-ready resolution (value/props/condition/issue). */\n resolve: (ref: BindingReference, item?: RepeaterItemContext) => BindingResolution\n /** Resolve a binding to the concrete value the renderer injects (EC-086–089). */\n resolveValue: (ref: BindingReference, item?: RepeaterItemContext) => JsonValue | undefined\n /** Evaluate a condition against the captured payloads (EC-088). */\n resolveCondition: (condition: Condition, item?: RepeaterItemContext) => ConditionResolution\n}\n\nexport interface DataState {\n sources: DataSource[]\n /**\n * Ids provisioned or removed in THIS session. Hydration (which resolves\n * asynchronously) never overrides them — the editor's latest action wins\n * over whatever was persisted before it.\n */\n sessionIds: ReadonlySet<string>\n}\n\nexport type DataAction =\n | { type: 'remove'; id: string }\n | { type: 'provision'; source: DataSource }\n /** Merge the project's persisted source records in (persisted wins over seeds). */\n | { type: 'hydrate'; sources: DataSource[] }\n\nfunction upsert(sources: DataSource[], source: DataSource): DataSource[] {\n const exists = sources.some((s) => s.id === source.id)\n return exists ? sources.map((s) => (s.id === source.id ? source : s)) : [...sources, source]\n}\n\n/**\n * A persisted record hydrates as-is when it carries its captured sample;\n * otherwise recapture. Exception (EC-177 gap 1): with a hydrated content\n * session configured (Convex mode), CMS sources ALWAYS recapture by replaying\n * their stored query over the live documents — a sample captured by an earlier\n * session may carry stale (or fixture-derived) entries.\n */\nfunction withSample(source: DataSource): DataSource {\n if (source.kind === 'cms' && hasContentSession()) {\n const sample = captureSourceSample(source)\n return sample !== undefined ? captureSample(source, sample) : source\n }\n if (source.sample !== undefined) return source\n // No persisted sample: try an honest capture (a provider runs; anything the\n // studio can't fetch returns undefined) — never fabricate.\n const sample = captureSourceSample(source)\n return sample !== undefined ? captureSample(source, sample) : source\n}\n\n/**\n * The store's reducer, exported because hydration/session-precedence semantics\n * live here and the app's test harness has no DOM to drive effects through.\n */\nexport function dataStateReducer(state: DataState, action: DataAction): DataState {\n switch (action.type) {\n case 'remove':\n return {\n ...state,\n sources: state.sources.filter((s) => s.id !== action.id),\n sessionIds: new Set([...state.sessionIds, action.id]),\n }\n case 'provision': {\n // Upsert by id; attach a sample only if one can be captured honestly (a\n // provider run, or live CMS content) — otherwise the source carries none.\n const sample = captureSourceSample(action.source)\n const provisioned = sample !== undefined ? captureSample(action.source, sample) : action.source\n return {\n ...state,\n sources: upsert(state.sources, provisioned),\n sessionIds: new Set([...state.sessionIds, provisioned.id]),\n }\n }\n case 'hydrate': {\n let sources = state.sources\n for (const source of action.sources) {\n if (state.sessionIds.has(source.id)) continue // this session already decided\n sources = upsert(sources, withSample(source))\n }\n return sources === state.sources ? state : { ...state, sources }\n }\n default:\n return state\n }\n}\n\nconst { Context: DataContext, useRequired: useData } = createRequiredContext<DataStore>('Data')\n\n/**\n * The session's starting state: EMPTY. The studio seeds no sample sources (Oli,\n * 2026-06-19) — real sources arrive from the project's config collections (the\n * hydrate effect, Convex mode) or are added by the author. Nothing is faked.\n */\nexport function initialDataState(): DataState {\n return { sources: [], sessionIds: new Set() }\n}\n\nexport interface DataProviderProps {\n children: ReactNode\n /**\n * When set, the store hydrates from and persists to this project's\n * `cms-data-sources` system collection (through `@elytracms/core/operations`).\n * Without it the store is pure session state (tests, fixtures).\n */\n projectId?: string\n}\n\nexport function DataProvider({ children, projectId }: DataProviderProps) {\n const [state, dispatch] = useReducer(dataStateReducer, undefined, initialDataState)\n const [persistError, setPersistError] = useState<string | null>(null)\n\n // EC-192: surface every config-driven content collection as a pickable CMS\n // source, so the binding/condition pickers list what actually exists — the\n // same collection set the CMS uses (`resolveDeploymentCollections`) — rather\n // than only the demo fixture sources. Merged additively (the `hydrate` upsert\n // skips session-decided ids and recaptures live samples in Convex mode); studio\n // SYSTEM collections (routes/redirects/saved views/data sources) are excluded.\n useEffect(() => {\n if (projectId === undefined) return\n const collections = resolveDeploymentCollections().filter(\n (collection) => !STUDIO_SYSTEM_COLLECTION_IDS.has(collection.id),\n )\n if (collections.length === 0) return\n dispatch({ type: 'hydrate', sources: collections.map(cmsSourceForCollection) })\n }, [projectId])\n\n // Hydrate the project's persisted sources through the one operations-backed\n // query cache (EC-308/AD-17). Sources provisioned by other sessions on the\n // same project show up here; the playground seeds stay for everything not\n // persisted. The reducer's `hydrate` keeps session precedence — a source\n // provisioned/removed in THIS session always wins over the hydrated list.\n const cache = useOperationCache()\n const sourcesQuery = useOperationQuery<DataSource[]>({\n queryKey: ['dataSources', projectId ?? '∅'],\n loader: async (operations) => {\n const result = await loadDataSources(operations, projectId as string)\n if (!result.ok) throw new Error(result.error.message)\n return result.value\n },\n enabled: projectId !== undefined,\n })\n const hydratedSources = sourcesQuery.data\n useEffect(() => {\n if (hydratedSources) dispatch({ type: 'hydrate', sources: hydratedSources })\n }, [hydratedSources])\n const sourcesError = sourcesQuery.isError ? sourcesQuery.error : null\n useEffect(() => {\n if (sourcesError)\n setPersistError(sourcesError instanceof Error ? sourcesError.message : 'Loading data sources failed.')\n }, [sourcesError])\n\n // Best-effort persistence on top of the live session: the canvas keeps\n // working on session state; a failed write surfaces via `persistError`. On a\n // confirmed write, invalidate the hydration query so a later remount\n // re-hydrates the provisioned/removed source rather than a stale list.\n const persist = useCallback(\n (write: (operations: OperationsClient, projectId: string) => Promise<OperationResult<void>>) => {\n if (projectId === undefined) return\n void getStudioOperations()\n .then(({ operations }) => write(operations, projectId))\n .then((result) => {\n if (!result.ok) {\n setPersistError(result.error.message)\n return\n }\n void cache.invalidate(['dataSources', projectId])\n })\n .catch((error: unknown) => {\n setPersistError(error instanceof Error ? error.message : 'Saving the data source failed.')\n })\n },\n [projectId, cache],\n )\n\n const store = useMemo<DataStore>(() => {\n const registry = SourceRegistry.from(state.sources)\n const payloads: Record<string, JsonValue> = {}\n for (const source of state.sources) {\n if (source.sample !== undefined) payloads[source.id] = source.sample\n }\n const knownSourceIds = registry.knownSourceIds()\n const ctx = { payloads, registry }\n\n const getSource = (id: string) => registry.get(id)\n const resolve = (ref: BindingReference, item?: RepeaterItemContext) =>\n resolveBinding(ref, ctx, item)\n\n return {\n sources: state.sources,\n registry,\n payloads,\n knownSourceIds,\n getSource,\n removeSource: (id) => {\n dispatch({ type: 'remove', id })\n persist((operations, pid) => deleteDataSource(operations, pid, id))\n },\n provisionSource: (source) => {\n dispatch({ type: 'provision', source })\n // Persist exactly what the session resolves: the source plus any sample\n // that can be captured honestly (a provider run / live CMS content) —\n // never a fabricated one. Matches the `provision` reducer.\n const sample = captureSourceSample(source)\n const provisioned = sample !== undefined ? captureSample(source, sample) : source\n persist((operations, pid) => saveDataSource(operations, pid, provisioned))\n },\n persistError,\n tokensOf: (id) => {\n const schema = getSource(id)?.schema\n return schema ? tokensForSchema(schema) : []\n },\n schemaOf: (id) => {\n const source = getSource(id)\n if (source?.schema) return source.schema\n if (source?.sample !== undefined) return inferSchema(source.sample)\n return undefined\n },\n sampleOf: (id) => getSource(id)?.sample,\n resolve,\n resolveValue: (ref, item) => {\n const r = resolve(ref, item)\n switch (r.kind) {\n case 'value':\n return r.value\n case 'props':\n return r.props\n case 'condition':\n return r.pass\n case 'issue':\n return undefined\n default: {\n const never: never = r\n return never\n }\n }\n },\n resolveCondition: (condition, item) => resolveCondition(condition, ctx, item),\n }\n }, [state, persist, persistError])\n\n return <DataContext.Provider value={store}>{children}</DataContext.Provider>\n}\n\nexport { useData }\n"]}
|
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* EC-268: instant-publish (EC-146) configuration health, studio-side.
|
|
3
|
-
* `delivery
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* EC-268: instant-publish (EC-146) configuration health, studio-side. Reads
|
|
3
|
+
* through `operations.delivery.config` (EC-329 — the operations seam, not a
|
|
4
|
+
* raw Convex client) to `PersistenceAdapter.delivery`, reporting whether each
|
|
5
|
+
* delivery env var is set (booleans only — never the values); these helpers
|
|
6
|
+
* turn that into the list the health banner shows. Kept pure + React-free so
|
|
7
|
+
* it is unit-testable and reusable (a future `elytra doctor`, EC-259, can
|
|
8
|
+
* format the same status).
|
|
7
9
|
*/
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
projectScope: boolean;
|
|
11
|
-
/** `CANVAS_REVALIDATE_ENDPOINT` — the host route the webhook POSTs to. */
|
|
12
|
-
revalidateEndpoint: boolean;
|
|
13
|
-
/** `CANVAS_REVALIDATE_SECRET` — shared HMAC secret (= host `REVALIDATE_SECRET`). */
|
|
14
|
-
revalidateSecret: boolean;
|
|
15
|
-
}
|
|
10
|
+
import type { DeliveryConfigStatus } from '@elytracms/core/persistence';
|
|
11
|
+
export type { DeliveryConfigStatus };
|
|
16
12
|
/** The pipeline env vars, in setup order, paired with the status key each maps to. */
|
|
17
13
|
export declare const INSTANT_PUBLISH_VARS: readonly [{
|
|
18
14
|
readonly key: "projectScope";
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* EC-268: instant-publish (EC-146) configuration health, studio-side. The Convex
|
|
3
|
-
* `delivery:deliveryConfig` query reports whether each delivery env var is set
|
|
4
|
-
* (booleans only — never the values); these helpers turn that into the list the
|
|
5
|
-
* health banner shows. Kept pure + React-free so it is unit-testable and reusable
|
|
6
|
-
* (a future `elytra doctor`, EC-259, can format the same status).
|
|
7
|
-
*/
|
|
8
1
|
/** The pipeline env vars, in setup order, paired with the status key each maps to. */
|
|
9
2
|
export const INSTANT_PUBLISH_VARS = [
|
|
10
3
|
{ key: 'projectScope', env: 'CANVAS_PROJECT_SCOPE' },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instant-publish.js","sourceRoot":"","sources":["../../../src/lib/delivery/instant-publish.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"instant-publish.js","sourceRoot":"","sources":["../../../src/lib/delivery/instant-publish.ts"],"names":[],"mappings":"AAYA,sFAAsF;AACtF,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,EAAE,GAAG,EAAE,cAAc,EAAE,GAAG,EAAE,sBAAsB,EAAE;IACpD,EAAE,GAAG,EAAE,oBAAoB,EAAE,GAAG,EAAE,4BAA4B,EAAE;IAChE,EAAE,GAAG,EAAE,kBAAkB,EAAE,GAAG,EAAE,0BAA0B,EAAE;CACsB,CAAA;AAEpF,qFAAqF;AACrF,MAAM,UAAU,yBAAyB,CAAC,MAA4B;IACpE,OAAO,oBAAoB,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;AAC7F,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,0BAA0B,CAAC,MAA4B;IACrE,OAAO,yBAAyB,CAAC,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC,CAAA;AACvD,CAAC","sourcesContent":["/**\n * EC-268: instant-publish (EC-146) configuration health, studio-side. Reads\n * through `operations.delivery.config` (EC-329 — the operations seam, not a\n * raw Convex client) to `PersistenceAdapter.delivery`, reporting whether each\n * delivery env var is set (booleans only — never the values); these helpers\n * turn that into the list the health banner shows. Kept pure + React-free so\n * it is unit-testable and reusable (a future `elytra doctor`, EC-259, can\n * format the same status).\n */\nimport type { DeliveryConfigStatus } from '@elytracms/core/persistence'\nexport type { DeliveryConfigStatus }\n\n/** The pipeline env vars, in setup order, paired with the status key each maps to. */\nexport const INSTANT_PUBLISH_VARS = [\n { key: 'projectScope', env: 'CANVAS_PROJECT_SCOPE' },\n { key: 'revalidateEndpoint', env: 'CANVAS_REVALIDATE_ENDPOINT' },\n { key: 'revalidateSecret', env: 'CANVAS_REVALIDATE_SECRET' },\n] as const satisfies ReadonlyArray<{ key: keyof DeliveryConfigStatus; env: string }>\n\n/** The env var names that are NOT set, in setup order (empty = fully configured). */\nexport function missingInstantPublishVars(status: DeliveryConfigStatus): string[] {\n return INSTANT_PUBLISH_VARS.filter((entry) => !status[entry.key]).map((entry) => entry.env)\n}\n\n/** True when every instant-publish env var is set. */\nexport function isInstantPublishConfigured(status: DeliveryConfigStatus): boolean {\n return missingInstantPublishVars(status).length === 0\n}\n"]}
|
|
@@ -6,16 +6,16 @@
|
|
|
6
6
|
* best-effort instead of a new persistence mechanism. Unavailable storage (SSR,
|
|
7
7
|
* privacy mode) degrades to the default every session — never a crash.
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
9
|
+
import { createEnumPreference } from './local-preference';
|
|
10
10
|
export const DIFF_VIEW_STORAGE_KEY = 'elytra-version-diff-view';
|
|
11
11
|
export const DEFAULT_DIFF_VIEW_MODE = 'sideBySide';
|
|
12
|
+
const preference = createEnumPreference(DIFF_VIEW_STORAGE_KEY, ['sideBySide', 'unified'], DEFAULT_DIFF_VIEW_MODE);
|
|
12
13
|
/** Read the persisted preference, or the default when unset/unavailable/invalid. */
|
|
13
14
|
export function readDiffViewMode() {
|
|
14
|
-
|
|
15
|
-
return value === 'sideBySide' || value === 'unified' ? value : DEFAULT_DIFF_VIEW_MODE;
|
|
15
|
+
return preference.read();
|
|
16
16
|
}
|
|
17
17
|
/** Persist the preference (best-effort; storage may be unavailable). */
|
|
18
18
|
export function storeDiffViewMode(mode) {
|
|
19
|
-
|
|
19
|
+
preference.store(mode);
|
|
20
20
|
}
|
|
21
21
|
//# sourceMappingURL=diff-view-preference.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diff-view-preference.js","sourceRoot":"","sources":["../../src/lib/diff-view-preference.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"diff-view-preference.js","sourceRoot":"","sources":["../../src/lib/diff-view-preference.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAIzD,MAAM,CAAC,MAAM,qBAAqB,GAAG,0BAA0B,CAAA;AAC/D,MAAM,CAAC,MAAM,sBAAsB,GAAiB,YAAY,CAAA;AAEhE,MAAM,UAAU,GAAG,oBAAoB,CACrC,qBAAqB,EACrB,CAAC,YAAY,EAAE,SAAS,CAAC,EACzB,sBAAsB,CACvB,CAAA;AAED,oFAAoF;AACpF,MAAM,UAAU,gBAAgB;IAC9B,OAAO,UAAU,CAAC,IAAI,EAAE,CAAA;AAC1B,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,iBAAiB,CAAC,IAAkB;IAClD,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;AACxB,CAAC","sourcesContent":["/**\n * Version-compare view-mode preference (EC-317). The unified-vs-side-by-side\n * toggle is a **transient, personal-local** UI preference — the same class as\n * the theme (`lib/theme.ts`) and rail-collapse (`lib/rail.ts`) prefs: it never\n * touches the project graph or any document, so it rides `localStorage`\n * best-effort instead of a new persistence mechanism. Unavailable storage (SSR,\n * privacy mode) degrades to the default every session — never a crash.\n */\nimport { createEnumPreference } from './local-preference'\n\nexport type DiffViewMode = 'sideBySide' | 'unified'\n\nexport const DIFF_VIEW_STORAGE_KEY = 'elytra-version-diff-view'\nexport const DEFAULT_DIFF_VIEW_MODE: DiffViewMode = 'sideBySide'\n\nconst preference = createEnumPreference<DiffViewMode>(\n DIFF_VIEW_STORAGE_KEY,\n ['sideBySide', 'unified'],\n DEFAULT_DIFF_VIEW_MODE,\n)\n\n/** Read the persisted preference, or the default when unset/unavailable/invalid. */\nexport function readDiffViewMode(): DiffViewMode {\n return preference.read()\n}\n\n/** Persist the preference (best-effort; storage may be unavailable). */\nexport function storeDiffViewMode(mode: DiffViewMode): void {\n preference.store(mode)\n}\n"]}
|
|
@@ -130,6 +130,7 @@ export const EN_MESSAGES = {
|
|
|
130
130
|
'shell.unsavedChanges.body': msg('You have unsaved changes. Save them before leaving, or discard them.'),
|
|
131
131
|
'shell.unsavedChanges.saving': msg('Saving…'),
|
|
132
132
|
'shell.devLogin.badge': msg('Dev login'),
|
|
133
|
+
'shell.devLogin.hint': msg('Dev auto-login: the studio signed in automatically from VITE_ELYTRA_DEV_LOGIN (dev builds only). Unset it to test the real sign-in flow.'),
|
|
133
134
|
'shell.playground.badge': msg('Playground', 'Product term — may stay untranslated.'),
|
|
134
135
|
'shell.playground.hint': msg('Playground mode: data lives in memory on this device and resets on reload. Nothing is saved to a backend.'),
|
|
135
136
|
'shell.deliveryHealth.dismiss': msg('Dismiss'),
|
|
@@ -227,8 +228,10 @@ export const EN_MESSAGES = {
|
|
|
227
228
|
'cms.editor.unsavedChanges': msg('Unsaved changes'),
|
|
228
229
|
'cms.editor.saved': msg('Saved'),
|
|
229
230
|
'cms.editor.saving': msg('Saving…'),
|
|
231
|
+
// 0.0.42 (round 3): the standalone "View live" button is retired — opening the live
|
|
232
|
+
// page now hangs off the header URL row's leaf segment. These strings still drive the
|
|
233
|
+
// leaf's tooltip (open-live target + the published-gating reasons).
|
|
230
234
|
'cms.editor.openLiveUrl': msg('Open {path}'),
|
|
231
|
-
'cms.editor.viewLive': msg('View live'),
|
|
232
235
|
'cms.editor.loadingLiveUrl': msg('Loading the live URL…'),
|
|
233
236
|
'cms.editor.publishToViewLive': msg('Publish to view the live page'),
|
|
234
237
|
'cms.editor.setFrontendUrlHint': msg('Set frontendUrl in elytra.config.ts to enable'),
|
|
@@ -299,10 +302,9 @@ export const EN_MESSAGES = {
|
|
|
299
302
|
'cms.details.slotInUse': msg(' — in use', 'Suffix on a slot option already taken by another document.'),
|
|
300
303
|
'cms.details.slotHint': msg('A stable name the site uses to find this document (e.g. the main menu), instead of which exact entry it is. One document per slot.'),
|
|
301
304
|
'cms.details.empty': msg('No document properties to set.'),
|
|
302
|
-
//
|
|
303
|
-
//
|
|
304
|
-
// their editors
|
|
305
|
-
'cms.details.url': msg('URL', 'Details pane section: the document’s composed URL path.'),
|
|
305
|
+
// The composed-URL affordance (EC-315 "hybrid split") — moved to the detail-editor
|
|
306
|
+
// HEADER under the title in 0.0.42 (round 3); ancestor-document segments link to
|
|
307
|
+
// their editors, static route segments are plain, and the leaf carries the live link.
|
|
306
308
|
'cms.details.urlCurrent': msg('this page', 'Tooltip on the final URL segment — the document being edited.'),
|
|
307
309
|
// ─────────────────────────────────────────────────────────────────────────
|
|
308
310
|
// CMS — import dialog
|