@elytracms/studio 0.0.62 → 0.0.63

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.
Files changed (78) hide show
  1. package/dist/components/account/credential-panel.js +6 -1
  2. package/dist/components/account/credential-panel.js.map +1 -1
  3. package/dist/components/auth-context.d.ts +14 -0
  4. package/dist/components/auth-context.js +32 -2
  5. package/dist/components/auth-context.js.map +1 -1
  6. package/dist/components/builder/blocks-model.d.ts +7 -0
  7. package/dist/components/builder/blocks-model.js +5 -0
  8. package/dist/components/builder/blocks-model.js.map +1 -1
  9. package/dist/components/builder/blocks-panel.js +2 -2
  10. package/dist/components/builder/blocks-panel.js.map +1 -1
  11. package/dist/components/builder/fixture-thumbnail.d.ts +15 -0
  12. package/dist/components/builder/fixture-thumbnail.js +14 -0
  13. package/dist/components/builder/fixture-thumbnail.js.map +1 -1
  14. package/dist/components/cms/cms-index.js +6 -3
  15. package/dist/components/cms/cms-index.js.map +1 -1
  16. package/dist/components/deployment-setup-gate.js +40 -8
  17. package/dist/components/deployment-setup-gate.js.map +1 -1
  18. package/dist/components/operation-error.d.ts +7 -5
  19. package/dist/components/operation-error.js +19 -36
  20. package/dist/components/operation-error.js.map +1 -1
  21. package/dist/components/project-list.js +17 -1
  22. package/dist/components/project-list.js.map +1 -1
  23. package/dist/components/project-switcher.d.ts +10 -0
  24. package/dist/components/project-switcher.js +13 -3
  25. package/dist/components/project-switcher.js.map +1 -1
  26. package/dist/components/session-expired-overlay.d.ts +27 -0
  27. package/dist/components/session-expired-overlay.js +51 -0
  28. package/dist/components/session-expired-overlay.js.map +1 -0
  29. package/dist/components/sign-in-view.d.ts +13 -1
  30. package/dist/components/sign-in-view.js +27 -13
  31. package/dist/components/sign-in-view.js.map +1 -1
  32. package/dist/components/studio-project-screen.js +2 -2
  33. package/dist/components/studio-project-screen.js.map +1 -1
  34. package/dist/components/studio-shell.js +40 -3
  35. package/dist/components/studio-shell.js.map +1 -1
  36. package/dist/customization.d.ts +4 -1
  37. package/dist/customization.js +3 -0
  38. package/dist/customization.js.map +1 -1
  39. package/dist/lib/auth-errors.d.ts +58 -28
  40. package/dist/lib/auth-errors.js +93 -15
  41. package/dist/lib/auth-errors.js.map +1 -1
  42. package/dist/lib/i18n/index.d.ts +1 -0
  43. package/dist/lib/i18n/index.js.map +1 -1
  44. package/dist/lib/i18n/messages.de.d.ts +24 -0
  45. package/dist/lib/i18n/messages.de.js +1019 -0
  46. package/dist/lib/i18n/messages.de.js.map +1 -0
  47. package/dist/lib/i18n/messages.en.d.ts +880 -0
  48. package/dist/lib/i18n/messages.en.js +71 -3
  49. package/dist/lib/i18n/messages.en.js.map +1 -1
  50. package/dist/lib/operations/studio-operations.js +7 -0
  51. package/dist/lib/operations/studio-operations.js.map +1 -1
  52. package/dist/lib/persistence/deployment-readiness.d.ts +8 -1
  53. package/dist/lib/persistence/deployment-readiness.js +16 -1
  54. package/dist/lib/persistence/deployment-readiness.js.map +1 -1
  55. package/dist/lib/session-expiry.d.ts +67 -0
  56. package/dist/lib/session-expiry.js +72 -0
  57. package/dist/lib/session-expiry.js.map +1 -0
  58. package/dist/lib/tab-title.d.ts +53 -0
  59. package/dist/lib/tab-title.js +77 -0
  60. package/dist/lib/tab-title.js.map +1 -0
  61. package/dist/lib/workspace/index.d.ts +1 -1
  62. package/dist/lib/workspace/index.js +1 -1
  63. package/dist/lib/workspace/index.js.map +1 -1
  64. package/dist/lib/workspace/switcher-source.d.ts +27 -0
  65. package/dist/lib/workspace/switcher-source.js +30 -0
  66. package/dist/lib/workspace/switcher-source.js.map +1 -1
  67. package/dist/router/index.d.ts +6 -0
  68. package/dist/router/index.js +6 -0
  69. package/dist/router/index.js.map +1 -1
  70. package/dist/router/launcher-visit.d.ts +13 -0
  71. package/dist/router/launcher-visit.js +34 -0
  72. package/dist/router/launcher-visit.js.map +1 -0
  73. package/dist/router/root.js +12 -2
  74. package/dist/router/root.js.map +1 -1
  75. package/dist/ui/dialog.d.ts +15 -0
  76. package/dist/ui/dialog.js +17 -0
  77. package/dist/ui/dialog.js.map +1 -1
  78. package/package.json +6 -2
@@ -1 +1 @@
1
- {"version":3,"file":"customization.js","sourceRoot":"","sources":["../src/customization.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAA;AAOnE,OAAO,EAAE,GAAG,EAAE,MAAM,wCAAwC,CAAA;AAO5D,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC9C,oFAAoF;AACpF,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAA;AACpE;;;;GAIG;AACH,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAA","sourcesContent":["/**\n * `@elytracms/studio/customization` — the public customization API for a consuming\n * repo's `studio.config.tsx` (EC-236/237/238/239).\n *\n * Everything a repo customizes about the builder UI is **live studio code passed by\n * reference** through `defineStudio({...})` — the section rail (`nav.*`), custom\n * field renderers, and (in time) branding. This config is studio-only: it is handed\n * to `createStudio(config, studioConfig)` at the mount and never enters the shared\n * `elytra.config.ts` / the `frontend/` delivery bundle. The customization principle\n * in one import surface: direct values, no registries, no string ids — the lone\n * string seam is `fieldRenderers`' control key, which the delivery-shared schema\n * references via `form.control`.\n *\n * @example\n * ```tsx\n * // studio.config.tsx\n * import { defineStudio, nav } from '@elytracms/studio/customization'\n * import { ColorField } from './fields/color'\n *\n * export default defineStudio({\n * sidebar: [\n * nav.cms({ label: 'Content' }),\n * nav.group('Catalog', [\n * nav.cms({ label: 'Catalog', collections: ['product', 'productCategory'] }),\n * nav.cms({ label: 'Categories', collection: 'productCategory' }),\n * ]),\n * nav.separator(),\n * nav.assets(),\n * nav.settings(),\n * ],\n * fieldRenderers: { color: ColorField },\n * })\n *\n * // router.tsx (the mount)\n * import { createStudio } from '@elytracms/studio/customization'\n * import config from '../../elytra.config'\n * import studioConfig from './studio.config'\n * createStudio(config, studioConfig)\n * ```\n */\nexport { defineStudio } from './components/app-views/studio-config'\nexport type {\n StudioConfig,\n StudioCanvasTheme,\n CustomFieldRenderer,\n CustomFieldRendererProps,\n} from './components/app-views/studio-config'\nexport { nav } from './components/app-views/default-modules'\nexport type {\n CmsViewConfig,\n BuiltinViewConfig,\n CustomViewConfig,\n} from './components/app-views/default-modules'\nexport type { AppViewContext, AppViewModule, SidebarNode } from './components/app-views/module'\nexport { createStudio } from './lib/workspace'\n/** A ready-made example renderer; a consuming repo may use it or author its own. */\nexport { colorFieldRenderer } from './components/cms/field-renderer'\n/**\n * Studio-UI localization (EC-241): author a translation catalog for `defineStudio({\n * i18n })`. `msg` is only needed to build an in-repo *source* catalog with translator\n * hints; a plain `MessageCatalog` (flat key → string) is all a consumer supplies.\n */\nexport { msg } from './lib/i18n'\nexport type { MessageCatalog, SourceMessage, StudioI18nConfig } from './lib/i18n'\n"]}
1
+ {"version":3,"file":"customization.js","sourceRoot":"","sources":["../src/customization.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAA;AAOnE,OAAO,EAAE,GAAG,EAAE,MAAM,wCAAwC,CAAA;AAO5D,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC9C,oFAAoF;AACpF,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAA;AACpE;;;;;;;GAOG;AACH,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAA","sourcesContent":["/**\n * `@elytracms/studio/customization` — the public customization API for a consuming\n * repo's `studio.config.tsx` (EC-236/237/238/239).\n *\n * Everything a repo customizes about the builder UI is **live studio code passed by\n * reference** through `defineStudio({...})` — the section rail (`nav.*`), custom\n * field renderers, and (in time) branding. This config is studio-only: it is handed\n * to `createStudio(config, studioConfig)` at the mount and never enters the shared\n * `elytra.config.ts` / the `frontend/` delivery bundle. The customization principle\n * in one import surface: direct values, no registries, no string ids — the lone\n * string seam is `fieldRenderers`' control key, which the delivery-shared schema\n * references via `form.control`.\n *\n * @example\n * ```tsx\n * // studio.config.tsx\n * import { defineStudio, nav } from '@elytracms/studio/customization'\n * import { ColorField } from './fields/color'\n *\n * export default defineStudio({\n * sidebar: [\n * nav.cms({ label: 'Content' }),\n * nav.group('Catalog', [\n * nav.cms({ label: 'Catalog', collections: ['product', 'productCategory'] }),\n * nav.cms({ label: 'Categories', collection: 'productCategory' }),\n * ]),\n * nav.separator(),\n * nav.assets(),\n * nav.settings(),\n * ],\n * fieldRenderers: { color: ColorField },\n * })\n *\n * // router.tsx (the mount)\n * import { createStudio } from '@elytracms/studio/customization'\n * import config from '../../elytra.config'\n * import studioConfig from './studio.config'\n * createStudio(config, studioConfig)\n * ```\n */\nexport { defineStudio } from './components/app-views/studio-config'\nexport type {\n StudioConfig,\n StudioCanvasTheme,\n CustomFieldRenderer,\n CustomFieldRendererProps,\n} from './components/app-views/studio-config'\nexport { nav } from './components/app-views/default-modules'\nexport type {\n CmsViewConfig,\n BuiltinViewConfig,\n CustomViewConfig,\n} from './components/app-views/default-modules'\nexport type { AppViewContext, AppViewModule, SidebarNode } from './components/app-views/module'\nexport { createStudio } from './lib/workspace'\n/** A ready-made example renderer; a consuming repo may use it or author its own. */\nexport { colorFieldRenderer } from './components/cms/field-renderer'\n/**\n * Studio-UI localization (EC-241): author a translation catalog for `defineStudio({\n * i18n })`. `msg` is only needed to build an in-repo *source* catalog with translator\n * hints; a plain `MessageCatalog` (flat key → string) is all a consumer supplies.\n * Prefer `TranslationCatalog` (EC-398) when authoring: it is typed against the EN key\n * set, so unknown/stale keys fail typecheck — engine-shipped locales\n * (`@elytracms/studio/locales/de`) and spread-overrides of them use it.\n */\nexport { msg } from './lib/i18n'\nexport type {\n MessageCatalog,\n MessageKey,\n SourceMessage,\n StudioI18nConfig,\n TranslationCatalog,\n} from './lib/i18n'\n"]}
@@ -1,23 +1,5 @@
1
+ import type { ChangePasswordResult, SignInResult } from './auth/auth-adapter.js';
1
2
  import type { OperationError } from './operations/index.js';
2
- /**
3
- * Session-expiry detection + plain-language mapping (EC-177 gap 9).
4
- *
5
- * An expired Convex session surfaces as a raw backend blob thrown by the
6
- * transport — e.g. `{"code":"Unauthenticated","message":"Could not verify
7
- * OIDC token claim…"}` — which `toOperationError` can only normalize to an
8
- * `unknown`/`backend-unavailable` `OperationError` whose message *is* that
9
- * blob. Rendering it verbatim is editor-hostile, and the real problem (edits
10
- * silently stop persisting) is invisible.
11
- *
12
- * This module is the one chokepoint that recognizes that shape on the
13
- * structured `OperationError` (the seam every studio failure already flows
14
- * through), so UI surfaces never string-match raw messages themselves: the
15
- * shared banner asks {@link isSessionExpiredError} and renders the
16
- * plain-language copy + a "Sign in again" action instead.
17
- */
18
- /** Editor-facing copy: plain language, no auth/transport jargon. */
19
- export declare const SESSION_EXPIRED_MESSAGE = "You've been signed out, so your latest changes aren't being saved. Sign in again to keep editing \u2014 you'll come back to this screen.";
20
- export declare const SIGN_IN_AGAIN_LABEL = "Sign in again";
21
3
  /**
22
4
  * Does this operation failure mean "the session is no longer valid"?
23
5
  *
@@ -29,14 +11,62 @@ export declare const SIGN_IN_AGAIN_LABEL = "Sign in again";
29
11
  * message it ships with.
30
12
  */
31
13
  export declare function isSessionExpiredError(error: OperationError): boolean;
14
+ type SignInFailure = Extract<SignInResult, {
15
+ ok: false;
16
+ }>;
17
+ /**
18
+ * Engine-owned sign-in-failure → message-catalog-key mapping (EC-397). The
19
+ * auth adapters (no-touch `lib/auth/**`) return plain-English `message`
20
+ * constants on the EC-362/EC-363 classified reasons; this table names the
21
+ * catalog key that carries each of those messages, so the sign-in screen
22
+ * localizes failures through the SAME catalog as the rest of the chrome
23
+ * (a German catalog covers them with no consumer-side mapper — the
24
+ * pre-EC-375 Naturkinder `authErrorMessage` is retired by this seam).
25
+ *
26
+ * A reason can carry several distinct engine messages (`unavailable` spans
27
+ * the rate-limit, the honest server-error generic, and the playground
28
+ * sign-up refusal), so each reason lists its candidate keys and a failure is
29
+ * recognized by **exact EN-message match** against the catalog entry. That
30
+ * makes English rendering byte-identical by construction and keeps the
31
+ * fallback honest: any unrecognized message (a future reason, a reworded
32
+ * adapter constant, a non-engine adapter) falls through to the raw
33
+ * `result.message` — never a mistranslated or stale mapping.
34
+ * `lib/auth-errors.test.ts` runs the real classifier over every branch to
35
+ * pin that adapter copy and catalog never drift apart silently.
36
+ */
37
+ export declare const SIGN_IN_ERROR_KEYS: Record<SignInFailure['reason'], readonly string[]>;
38
+ /**
39
+ * Resolve a failed {@link SignInResult} to its message-catalog key, or `null`
40
+ * when the failure is not an engine-known shape (render `result.message` raw
41
+ * — the honest fallback). Pure; accepts the failure structurally so callers
42
+ * and tests can probe unknown future reasons without casts.
43
+ */
44
+ export declare function signInErrorMessageKey(failure: {
45
+ reason: string;
46
+ message: string;
47
+ }): string | null;
48
+ type ChangePasswordFailure = Extract<ChangePasswordResult, {
49
+ ok: false;
50
+ }>;
51
+ /**
52
+ * Engine-owned change-password-failure → message-catalog-key mapping (EC-412)
53
+ * — the {@link SIGN_IN_ERROR_KEYS} design extended to the account credential
54
+ * panel. The reasons are the {@link ChangePasswordResult} failure union;
55
+ * `unavailable` again spans several deliberate messages (the
56
+ * deployment-misconfigured remedy, the EC-377 rate limit, and the honest
57
+ * server-error generic), so recognition is by reason + exact EN-message match
58
+ * and every unrecognized shape falls through to the raw `result.message`.
59
+ * `lib/auth-errors.test.ts` runs the real `classifyChangePasswordError` over
60
+ * every branch to pin adapter copy and catalog together.
61
+ */
62
+ export declare const CHANGE_PASSWORD_ERROR_KEYS: Record<ChangePasswordFailure['reason'], readonly string[]>;
32
63
  /**
33
- * Where the "Sign in again" action navigates: the sign-in screen, carrying the
34
- * current location so a successful sign-in returns the editor to the exact
35
- * screen they were on (the sign-in route's `redirect` search param, EC-150).
36
- * External/protocol-relative targets are rejected, mirroring
37
- * `postSignInTarget`.
64
+ * Resolve a failed {@link ChangePasswordResult} to its message-catalog key, or
65
+ * `null` when the failure is not an engine-known shape (render
66
+ * `result.message` raw — the honest fallback).
38
67
  */
39
- export declare function signInHref(location: {
40
- pathname: string;
41
- search?: string;
42
- }): string;
68
+ export declare function changePasswordErrorMessageKey(failure: {
69
+ reason: string;
70
+ message: string;
71
+ }): string | null;
72
+ export {};
@@ -1,3 +1,4 @@
1
+ import { EN_MESSAGES } from './i18n/messages.en.js';
1
2
  /**
2
3
  * Session-expiry detection + plain-language mapping (EC-177 gap 9).
3
4
  *
@@ -10,13 +11,13 @@
10
11
  *
11
12
  * This module is the one chokepoint that recognizes that shape on the
12
13
  * structured `OperationError` (the seam every studio failure already flows
13
- * through), so UI surfaces never string-match raw messages themselves: the
14
- * shared banner asks {@link isSessionExpiredError} and renders the
15
- * plain-language copy + a "Sign in again" action instead.
14
+ * through), so UI surfaces never string-match raw messages themselves. Since
15
+ * EC-403 an expired session is an app-level state: `lib/session-expiry.ts`
16
+ * runs this predicate over every failed operation on the lifecycle bus and
17
+ * raises the blocking in-place re-auth overlay (the
18
+ * `error.sessionExpiredTitle` / `error.sessionExpired` catalog keys) — the
19
+ * shared banner only uses it to route expiry AWAY from per-surface rendering.
16
20
  */
17
- /** Editor-facing copy: plain language, no auth/transport jargon. */
18
- export const SESSION_EXPIRED_MESSAGE = "You've been signed out, so your latest changes aren't being saved. Sign in again to keep editing — you'll come back to this screen.";
19
- export const SIGN_IN_AGAIN_LABEL = 'Sign in again';
20
21
  /**
21
22
  * Extract the `code` of a JSON object embedded in a transport error message.
22
23
  * Convex auth failures carry `{"code":"Unauthenticated", …}`, sometimes
@@ -60,15 +61,92 @@ export function isSessionExpiredError(error) {
60
61
  return /could not verify oidc token/i.test(error.message);
61
62
  }
62
63
  /**
63
- * Where the "Sign in again" action navigates: the sign-in screen, carrying the
64
- * current location so a successful sign-in returns the editor to the exact
65
- * screen they were on (the sign-in route's `redirect` search param, EC-150).
66
- * External/protocol-relative targets are rejected, mirroring
67
- * `postSignInTarget`.
64
+ * Engine-owned sign-in-failure → message-catalog-key mapping (EC-397). The
65
+ * auth adapters (no-touch `lib/auth/**`) return plain-English `message`
66
+ * constants on the EC-362/EC-363 classified reasons; this table names the
67
+ * catalog key that carries each of those messages, so the sign-in screen
68
+ * localizes failures through the SAME catalog as the rest of the chrome
69
+ * (a German catalog covers them with no consumer-side mapper — the
70
+ * pre-EC-375 Naturkinder `authErrorMessage` is retired by this seam).
71
+ *
72
+ * A reason can carry several distinct engine messages (`unavailable` spans
73
+ * the rate-limit, the honest server-error generic, and the playground
74
+ * sign-up refusal), so each reason lists its candidate keys and a failure is
75
+ * recognized by **exact EN-message match** against the catalog entry. That
76
+ * makes English rendering byte-identical by construction and keeps the
77
+ * fallback honest: any unrecognized message (a future reason, a reworded
78
+ * adapter constant, a non-engine adapter) falls through to the raw
79
+ * `result.message` — never a mistranslated or stale mapping.
80
+ * `lib/auth-errors.test.ts` runs the real classifier over every branch to
81
+ * pin that adapter copy and catalog never drift apart silently.
82
+ */
83
+ export const SIGN_IN_ERROR_KEYS = {
84
+ 'invalid-credentials': [
85
+ 'auth.signIn.error.invalidCredentials',
86
+ 'auth.signIn.error.invalidCredentialsSignUp',
87
+ ],
88
+ 'account-exists': ['auth.signIn.error.accountExists'],
89
+ 'deployment-misconfigured': ['auth.signIn.error.deploymentMisconfigured'],
90
+ 'not-invited': ['auth.signIn.error.notInvited'],
91
+ unavailable: [
92
+ 'auth.signIn.error.tooManyAttempts',
93
+ 'auth.signIn.error.serverError',
94
+ 'auth.signIn.error.serverErrorSignUp',
95
+ 'auth.signIn.error.unavailable',
96
+ 'auth.signIn.error.playgroundSignUp',
97
+ ],
98
+ };
99
+ /**
100
+ * Resolve a failure `{ reason, message }` against a reason → candidate-keys
101
+ * table by **exact EN-message match** (the EC-397 recognition rule): the
102
+ * matched key's catalog entry carries the same English, so EN rendering stays
103
+ * byte-identical, and anything unrecognized returns `null` — the caller
104
+ * renders the raw message, never a stale mapping.
105
+ */
106
+ function errorMessageKey(table, failure) {
107
+ const candidates = table[failure.reason];
108
+ return candidates?.find((key) => EN_MESSAGES[key]?.message === failure.message) ?? null;
109
+ }
110
+ /**
111
+ * Resolve a failed {@link SignInResult} to its message-catalog key, or `null`
112
+ * when the failure is not an engine-known shape (render `result.message` raw
113
+ * — the honest fallback). Pure; accepts the failure structurally so callers
114
+ * and tests can probe unknown future reasons without casts.
115
+ */
116
+ export function signInErrorMessageKey(failure) {
117
+ return errorMessageKey(SIGN_IN_ERROR_KEYS, failure);
118
+ }
119
+ /**
120
+ * Engine-owned change-password-failure → message-catalog-key mapping (EC-412)
121
+ * — the {@link SIGN_IN_ERROR_KEYS} design extended to the account credential
122
+ * panel. The reasons are the {@link ChangePasswordResult} failure union;
123
+ * `unavailable` again spans several deliberate messages (the
124
+ * deployment-misconfigured remedy, the EC-377 rate limit, and the honest
125
+ * server-error generic), so recognition is by reason + exact EN-message match
126
+ * and every unrecognized shape falls through to the raw `result.message`.
127
+ * `lib/auth-errors.test.ts` runs the real `classifyChangePasswordError` over
128
+ * every branch to pin adapter copy and catalog together.
129
+ */
130
+ export const CHANGE_PASSWORD_ERROR_KEYS = {
131
+ 'invalid-credentials': ['account.password.error.currentIncorrect'],
132
+ 'weak-password': ['account.password.error.weakPassword'],
133
+ 'not-authenticated': ['account.password.error.notSignedIn'],
134
+ unavailable: [
135
+ 'account.password.error.deploymentMisconfigured',
136
+ 'account.password.error.tooManyAttempts',
137
+ 'account.password.error.serverError',
138
+ ],
139
+ unsupported: ['account.password.error.playgroundUnsupported'],
140
+ };
141
+ /**
142
+ * Resolve a failed {@link ChangePasswordResult} to its message-catalog key, or
143
+ * `null` when the failure is not an engine-known shape (render
144
+ * `result.message` raw — the honest fallback).
68
145
  */
69
- export function signInHref(location) {
70
- const target = `${location.pathname}${location.search ?? ''}`;
71
- const redirect = target.startsWith('/') && !target.startsWith('//') ? target : '/';
72
- return `/sign-in?redirect=${encodeURIComponent(redirect)}`;
146
+ export function changePasswordErrorMessageKey(failure) {
147
+ return errorMessageKey(CHANGE_PASSWORD_ERROR_KEYS, failure);
73
148
  }
149
+ // `signInHref` (the banner's navigating "Sign in again" action) was retired by
150
+ // EC-403: re-auth happens in place via the blocking overlay — a navigation
151
+ // would destroy exactly the unsaved editor state the treatment protects.
74
152
  //# sourceMappingURL=auth-errors.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth-errors.js","sourceRoot":"","sources":["../../src/lib/auth-errors.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;GAeG;AAEH,oEAAoE;AACpE,MAAM,CAAC,MAAM,uBAAuB,GAClC,qIAAqI,CAAA;AAEvI,MAAM,CAAC,MAAM,mBAAmB,GAAG,eAAe,CAAA;AAElD;;;;GAIG;AACH,SAAS,gBAAgB,CAAC,OAAe;IACvC,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAClC,MAAM,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA;IACpC,IAAI,KAAK,KAAK,CAAC,CAAC,IAAI,GAAG,IAAI,KAAK;QAAE,OAAO,SAAS,CAAA;IAClD,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,CAAA;QACjE,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YAClD,MAAM,IAAI,GAAI,MAA6B,CAAC,IAAI,CAAA;YAChD,IAAI,OAAO,IAAI,KAAK,QAAQ;gBAAE,OAAO,IAAI,CAAA;QAC3C,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,0DAA0D;IAC5D,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAqB;IACzD,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,IAAI,KAAK,qBAAqB;QAAE,OAAO,KAAK,CAAA;IAClF,IAAI,gBAAgB,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,iBAAiB;QAAE,OAAO,IAAI,CAAA;IACtE,IAAI,qBAAqB,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAA;IAC1D,OAAO,8BAA8B,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;AAC3D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,QAA+C;IACxE,MAAM,MAAM,GAAG,GAAG,QAAQ,CAAC,QAAQ,GAAG,QAAQ,CAAC,MAAM,IAAI,EAAE,EAAE,CAAA;IAC7D,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAA;IAClF,OAAO,qBAAqB,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAA;AAC5D,CAAC","sourcesContent":["import type { OperationError } from './operations'\n\n/**\n * Session-expiry detection + plain-language mapping (EC-177 gap 9).\n *\n * An expired Convex session surfaces as a raw backend blob thrown by the\n * transport — e.g. `{\"code\":\"Unauthenticated\",\"message\":\"Could not verify\n * OIDC token claim…\"}` — which `toOperationError` can only normalize to an\n * `unknown`/`backend-unavailable` `OperationError` whose message *is* that\n * blob. Rendering it verbatim is editor-hostile, and the real problem (edits\n * silently stop persisting) is invisible.\n *\n * This module is the one chokepoint that recognizes that shape on the\n * structured `OperationError` (the seam every studio failure already flows\n * through), so UI surfaces never string-match raw messages themselves: the\n * shared banner asks {@link isSessionExpiredError} and renders the\n * plain-language copy + a \"Sign in again\" action instead.\n */\n\n/** Editor-facing copy: plain language, no auth/transport jargon. */\nexport const SESSION_EXPIRED_MESSAGE =\n \"You've been signed out, so your latest changes aren't being saved. Sign in again to keep editing — you'll come back to this screen.\"\n\nexport const SIGN_IN_AGAIN_LABEL = 'Sign in again'\n\n/**\n * Extract the `code` of a JSON object embedded in a transport error message.\n * Convex auth failures carry `{\"code\":\"Unauthenticated\", …}`, sometimes\n * wrapped in client prefixes (`[CONVEX M(graph:save)] … Server Error: {…}`).\n */\nfunction embeddedJsonCode(message: string): string | undefined {\n const start = message.indexOf('{')\n const end = message.lastIndexOf('}')\n if (start === -1 || end <= start) return undefined\n try {\n const parsed: unknown = JSON.parse(message.slice(start, end + 1))\n if (parsed !== null && typeof parsed === 'object') {\n const code = (parsed as { code?: unknown }).code\n if (typeof code === 'string') return code\n }\n } catch {\n // Not a JSON payload — fall through to the marker checks.\n }\n return undefined\n}\n\n/**\n * Does this operation failure mean \"the session is no longer valid\"?\n *\n * Only the transport-shaped codes can carry the backend auth blob: structured\n * studio-level failures (`validation`, `unauthorized` from the role hook,\n * `not-found`, `conflict`) are never session expiry. Within those, detect the\n * Convex unauthenticated shape: an embedded `{\"code\":\"Unauthenticated\"}`\n * payload, the bare `Unauthenticated` server-error marker, or the OIDC claim\n * message it ships with.\n */\nexport function isSessionExpiredError(error: OperationError): boolean {\n if (error.code !== 'unknown' && error.code !== 'backend-unavailable') return false\n if (embeddedJsonCode(error.message) === 'Unauthenticated') return true\n if (/\\bUnauthenticated\\b/.test(error.message)) return true\n return /could not verify oidc token/i.test(error.message)\n}\n\n/**\n * Where the \"Sign in again\" action navigates: the sign-in screen, carrying the\n * current location so a successful sign-in returns the editor to the exact\n * screen they were on (the sign-in route's `redirect` search param, EC-150).\n * External/protocol-relative targets are rejected, mirroring\n * `postSignInTarget`.\n */\nexport function signInHref(location: { pathname: string; search?: string }): string {\n const target = `${location.pathname}${location.search ?? ''}`\n const redirect = target.startsWith('/') && !target.startsWith('//') ? target : '/'\n return `/sign-in?redirect=${encodeURIComponent(redirect)}`\n}\n"]}
1
+ {"version":3,"file":"auth-errors.js","sourceRoot":"","sources":["../../src/lib/auth-errors.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ChangePasswordResult, SignInResult } from './auth/auth-adapter'\nimport { EN_MESSAGES } from './i18n/messages.en'\nimport type { OperationError } from './operations'\n\n/**\n * Session-expiry detection + plain-language mapping (EC-177 gap 9).\n *\n * An expired Convex session surfaces as a raw backend blob thrown by the\n * transport — e.g. `{\"code\":\"Unauthenticated\",\"message\":\"Could not verify\n * OIDC token claim…\"}` — which `toOperationError` can only normalize to an\n * `unknown`/`backend-unavailable` `OperationError` whose message *is* that\n * blob. Rendering it verbatim is editor-hostile, and the real problem (edits\n * silently stop persisting) is invisible.\n *\n * This module is the one chokepoint that recognizes that shape on the\n * structured `OperationError` (the seam every studio failure already flows\n * through), so UI surfaces never string-match raw messages themselves. Since\n * EC-403 an expired session is an app-level state: `lib/session-expiry.ts`\n * runs this predicate over every failed operation on the lifecycle bus and\n * raises the blocking in-place re-auth overlay (the\n * `error.sessionExpiredTitle` / `error.sessionExpired` catalog keys) — the\n * shared banner only uses it to route expiry AWAY from per-surface rendering.\n */\n\n/**\n * Extract the `code` of a JSON object embedded in a transport error message.\n * Convex auth failures carry `{\"code\":\"Unauthenticated\", …}`, sometimes\n * wrapped in client prefixes (`[CONVEX M(graph:save)] … Server Error: {…}`).\n */\nfunction embeddedJsonCode(message: string): string | undefined {\n const start = message.indexOf('{')\n const end = message.lastIndexOf('}')\n if (start === -1 || end <= start) return undefined\n try {\n const parsed: unknown = JSON.parse(message.slice(start, end + 1))\n if (parsed !== null && typeof parsed === 'object') {\n const code = (parsed as { code?: unknown }).code\n if (typeof code === 'string') return code\n }\n } catch {\n // Not a JSON payload — fall through to the marker checks.\n }\n return undefined\n}\n\n/**\n * Does this operation failure mean \"the session is no longer valid\"?\n *\n * Only the transport-shaped codes can carry the backend auth blob: structured\n * studio-level failures (`validation`, `unauthorized` from the role hook,\n * `not-found`, `conflict`) are never session expiry. Within those, detect the\n * Convex unauthenticated shape: an embedded `{\"code\":\"Unauthenticated\"}`\n * payload, the bare `Unauthenticated` server-error marker, or the OIDC claim\n * message it ships with.\n */\nexport function isSessionExpiredError(error: OperationError): boolean {\n if (error.code !== 'unknown' && error.code !== 'backend-unavailable') return false\n if (embeddedJsonCode(error.message) === 'Unauthenticated') return true\n if (/\\bUnauthenticated\\b/.test(error.message)) return true\n return /could not verify oidc token/i.test(error.message)\n}\n\ntype SignInFailure = Extract<SignInResult, { ok: false }>\n\n/**\n * Engine-owned sign-in-failure → message-catalog-key mapping (EC-397). The\n * auth adapters (no-touch `lib/auth/**`) return plain-English `message`\n * constants on the EC-362/EC-363 classified reasons; this table names the\n * catalog key that carries each of those messages, so the sign-in screen\n * localizes failures through the SAME catalog as the rest of the chrome\n * (a German catalog covers them with no consumer-side mapper — the\n * pre-EC-375 Naturkinder `authErrorMessage` is retired by this seam).\n *\n * A reason can carry several distinct engine messages (`unavailable` spans\n * the rate-limit, the honest server-error generic, and the playground\n * sign-up refusal), so each reason lists its candidate keys and a failure is\n * recognized by **exact EN-message match** against the catalog entry. That\n * makes English rendering byte-identical by construction and keeps the\n * fallback honest: any unrecognized message (a future reason, a reworded\n * adapter constant, a non-engine adapter) falls through to the raw\n * `result.message` — never a mistranslated or stale mapping.\n * `lib/auth-errors.test.ts` runs the real classifier over every branch to\n * pin that adapter copy and catalog never drift apart silently.\n */\nexport const SIGN_IN_ERROR_KEYS: Record<SignInFailure['reason'], readonly string[]> = {\n 'invalid-credentials': [\n 'auth.signIn.error.invalidCredentials',\n 'auth.signIn.error.invalidCredentialsSignUp',\n ],\n 'account-exists': ['auth.signIn.error.accountExists'],\n 'deployment-misconfigured': ['auth.signIn.error.deploymentMisconfigured'],\n 'not-invited': ['auth.signIn.error.notInvited'],\n unavailable: [\n 'auth.signIn.error.tooManyAttempts',\n 'auth.signIn.error.serverError',\n 'auth.signIn.error.serverErrorSignUp',\n 'auth.signIn.error.unavailable',\n 'auth.signIn.error.playgroundSignUp',\n ],\n}\n\n/**\n * Resolve a failure `{ reason, message }` against a reason → candidate-keys\n * table by **exact EN-message match** (the EC-397 recognition rule): the\n * matched key's catalog entry carries the same English, so EN rendering stays\n * byte-identical, and anything unrecognized returns `null` — the caller\n * renders the raw message, never a stale mapping.\n */\nfunction errorMessageKey(\n table: Record<string, readonly string[] | undefined>,\n failure: { reason: string; message: string },\n): string | null {\n const candidates = table[failure.reason]\n return candidates?.find((key) => EN_MESSAGES[key]?.message === failure.message) ?? null\n}\n\n/**\n * Resolve a failed {@link SignInResult} to its message-catalog key, or `null`\n * when the failure is not an engine-known shape (render `result.message` raw\n * — the honest fallback). Pure; accepts the failure structurally so callers\n * and tests can probe unknown future reasons without casts.\n */\nexport function signInErrorMessageKey(failure: {\n reason: string\n message: string\n}): string | null {\n return errorMessageKey(SIGN_IN_ERROR_KEYS, failure)\n}\n\ntype ChangePasswordFailure = Extract<ChangePasswordResult, { ok: false }>\n\n/**\n * Engine-owned change-password-failure → message-catalog-key mapping (EC-412)\n * — the {@link SIGN_IN_ERROR_KEYS} design extended to the account credential\n * panel. The reasons are the {@link ChangePasswordResult} failure union;\n * `unavailable` again spans several deliberate messages (the\n * deployment-misconfigured remedy, the EC-377 rate limit, and the honest\n * server-error generic), so recognition is by reason + exact EN-message match\n * and every unrecognized shape falls through to the raw `result.message`.\n * `lib/auth-errors.test.ts` runs the real `classifyChangePasswordError` over\n * every branch to pin adapter copy and catalog together.\n */\nexport const CHANGE_PASSWORD_ERROR_KEYS: Record<\n ChangePasswordFailure['reason'],\n readonly string[]\n> = {\n 'invalid-credentials': ['account.password.error.currentIncorrect'],\n 'weak-password': ['account.password.error.weakPassword'],\n 'not-authenticated': ['account.password.error.notSignedIn'],\n unavailable: [\n 'account.password.error.deploymentMisconfigured',\n 'account.password.error.tooManyAttempts',\n 'account.password.error.serverError',\n ],\n unsupported: ['account.password.error.playgroundUnsupported'],\n}\n\n/**\n * Resolve a failed {@link ChangePasswordResult} to its message-catalog key, or\n * `null` when the failure is not an engine-known shape (render\n * `result.message` raw — the honest fallback).\n */\nexport function changePasswordErrorMessageKey(failure: {\n reason: string\n message: string\n}): string | null {\n return errorMessageKey(CHANGE_PASSWORD_ERROR_KEYS, failure)\n}\n\n// `signInHref` (the banner's navigating \"Sign in again\" action) was retired by\n// EC-403: re-auth happens in place via the blocking overlay — a navigation\n// would destroy exactly the unsaved editor state the treatment protects.\n"]}
@@ -9,4 +9,5 @@
9
9
  export type { SourceMessage, SourceCatalog, MessageCatalog, StudioI18nConfig } from './types.js';
10
10
  export { msg } from './types.js';
11
11
  export { EN_MESSAGES } from './messages.en.js';
12
+ export type { MessageKey, TranslationCatalog } from './messages.en.js';
12
13
  export { resolveActiveLocale, resolveStudioI18n, translate, localizedLabel, msgLabel, resolveMsgLabel, useT, useUiLocale, useLabel, parseAcceptLanguage, setServerLanguagesResolver, setUiLocaleForTests, resetStudioI18nForTests, } from './i18n.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/i18n/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,GAAG,EAAE,MAAM,SAAS,CAAA;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,SAAS,EACT,cAAc,EACd,QAAQ,EACR,eAAe,EACf,IAAI,EACJ,WAAW,EACX,QAAQ,EACR,mBAAmB,EACnB,0BAA0B,EAC1B,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,QAAQ,CAAA","sourcesContent":["/**\n * `@elytracms/studio/lib/i18n` — studio-UI localization (EC-241).\n *\n * - Public config surface (`StudioI18nConfig`, `MessageCatalog`, `msg`) is re-exported\n * from `@elytracms/studio/customization` so a consumer authors catalogs there.\n * - Studio components localize chrome via `translate`/`useT` and schema labels via\n * `useLabel`.\n */\nexport type { SourceMessage, SourceCatalog, MessageCatalog, StudioI18nConfig } from './types'\nexport { msg } from './types'\nexport { EN_MESSAGES } from './messages.en'\nexport {\n resolveActiveLocale,\n resolveStudioI18n,\n translate,\n localizedLabel,\n msgLabel,\n resolveMsgLabel,\n useT,\n useUiLocale,\n useLabel,\n parseAcceptLanguage,\n setServerLanguagesResolver,\n setUiLocaleForTests,\n resetStudioI18nForTests,\n} from './i18n'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/i18n/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,GAAG,EAAE,MAAM,SAAS,CAAA;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAE3C,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,SAAS,EACT,cAAc,EACd,QAAQ,EACR,eAAe,EACf,IAAI,EACJ,WAAW,EACX,QAAQ,EACR,mBAAmB,EACnB,0BAA0B,EAC1B,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,QAAQ,CAAA","sourcesContent":["/**\n * `@elytracms/studio/lib/i18n` — studio-UI localization (EC-241).\n *\n * - Public config surface (`StudioI18nConfig`, `MessageCatalog`, `msg`) is re-exported\n * from `@elytracms/studio/customization` so a consumer authors catalogs there.\n * - Studio components localize chrome via `translate`/`useT` and schema labels via\n * `useLabel`.\n */\nexport type { SourceMessage, SourceCatalog, MessageCatalog, StudioI18nConfig } from './types'\nexport { msg } from './types'\nexport { EN_MESSAGES } from './messages.en'\nexport type { MessageKey, TranslationCatalog } from './messages.en'\nexport {\n resolveActiveLocale,\n resolveStudioI18n,\n translate,\n localizedLabel,\n msgLabel,\n resolveMsgLabel,\n useT,\n useUiLocale,\n useLabel,\n parseAcceptLanguage,\n setServerLanguagesResolver,\n setUiLocaleForTests,\n resetStudioI18nForTests,\n} from './i18n'\n"]}
@@ -0,0 +1,24 @@
1
+ import type { TranslationCatalog } from './messages.en.js';
2
+ /**
3
+ * The engine-shipped **German** studio-chrome catalog (EC-398), importable as
4
+ * `@elytracms/studio/locales/de` and passed by reference into
5
+ * `defineStudio({ i18n: { locales: { de: deMessages } } })` — translations stay
6
+ * code passed directly (EC-241's principle; no registry, no runtime fetch). A
7
+ * consumer overrides individual strings by spreading:
8
+ * `{ ...deMessages, 'some.key': 'eigene Formulierung' }`.
9
+ *
10
+ * Seeded 2026-07-15 as the best-of merge of the two hand-maintained consumer
11
+ * catalogs (Naturkinder wins conflicts) + fresh translations for the keys both
12
+ * lacked (incl. EC-397's sign-in/boot copy). Typed against
13
+ * {@link TranslationCatalog} so an unknown/stale key fails typecheck;
14
+ * `messages.de.test.ts` pins EXACT key parity with the EN source (no missing,
15
+ * no stale, matching `{var}` placeholders) — a new engine string fails the
16
+ * engine build until it is translated here.
17
+ *
18
+ * Sections mirror `messages.en.ts` so the files diff side-by-side.
19
+ *
20
+ * Glossar: Sammlung · Dokument · Feld · Medien · Komponente · Weiterleitung ·
21
+ * Verweis · Version · Entwurf · veröffentlichen · Benutzer · Rolle ·
22
+ * Einstellungen · Bereitstellung.
23
+ */
24
+ export declare const deMessages: TranslationCatalog;