@elytracms/studio 0.0.64 → 0.0.66

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 (182) hide show
  1. package/convex/assets.ts +11 -0
  2. package/convex/delivery.ts +5 -0
  3. package/convex/schema.ts +23 -0
  4. package/dist/components/account/self-account-view-source.js +9 -2
  5. package/dist/components/account/self-account-view-source.js.map +1 -1
  6. package/dist/components/app-views/default-modules.d.ts +28 -1
  7. package/dist/components/app-views/default-modules.js +47 -2
  8. package/dist/components/app-views/default-modules.js.map +1 -1
  9. package/dist/components/assets/asset-thumb.d.ts +4 -2
  10. package/dist/components/assets/asset-thumb.js +26 -4
  11. package/dist/components/assets/asset-thumb.js.map +1 -1
  12. package/dist/components/assets/assets-module.js +48 -2
  13. package/dist/components/assets/assets-module.js.map +1 -1
  14. package/dist/components/assets/assets-store.d.ts +11 -1
  15. package/dist/components/assets/assets-store.js +18 -1
  16. package/dist/components/assets/assets-store.js.map +1 -1
  17. package/dist/components/auth-context.js +32 -1
  18. package/dist/components/auth-context.js.map +1 -1
  19. package/dist/components/builder/blocks-model.d.ts +11 -0
  20. package/dist/components/builder/blocks-model.js +30 -10
  21. package/dist/components/builder/blocks-model.js.map +1 -1
  22. package/dist/components/builder/canvas-chrome-css.js +90 -8
  23. package/dist/components/builder/canvas-chrome-css.js.map +1 -1
  24. package/dist/components/builder/canvas-dnd.js +4 -2
  25. package/dist/components/builder/canvas-dnd.js.map +1 -1
  26. package/dist/components/builder/canvas-inline-editing.d.ts +7 -0
  27. package/dist/components/builder/canvas-inline-editing.js +3 -1
  28. package/dist/components/builder/canvas-inline-editing.js.map +1 -1
  29. package/dist/components/builder/canvas-overlay-geometry.d.ts +72 -0
  30. package/dist/components/builder/canvas-overlay-geometry.js +80 -0
  31. package/dist/components/builder/canvas-overlay-geometry.js.map +1 -0
  32. package/dist/components/builder/canvas-rect-tracking.d.ts +124 -0
  33. package/dist/components/builder/canvas-rect-tracking.js +198 -0
  34. package/dist/components/builder/canvas-rect-tracking.js.map +1 -0
  35. package/dist/components/builder/canvas.js +61 -12
  36. package/dist/components/builder/canvas.js.map +1 -1
  37. package/dist/components/builder/composition-field-editor.js +22 -3
  38. package/dist/components/builder/composition-field-editor.js.map +1 -1
  39. package/dist/components/builder/delete-key.d.ts +38 -0
  40. package/dist/components/builder/delete-key.js +46 -0
  41. package/dist/components/builder/delete-key.js.map +1 -0
  42. package/dist/components/builder/drag-session.d.ts +8 -0
  43. package/dist/components/builder/drag-session.js +15 -0
  44. package/dist/components/builder/drag-session.js.map +1 -1
  45. package/dist/components/builder/insert-affordance-context.d.ts +19 -0
  46. package/dist/components/builder/insert-affordance-context.js +7 -0
  47. package/dist/components/builder/insert-affordance-context.js.map +1 -0
  48. package/dist/components/builder/insert-context.d.ts +40 -0
  49. package/dist/components/builder/insert-context.js +97 -5
  50. package/dist/components/builder/insert-context.js.map +1 -1
  51. package/dist/components/builder/node-title.d.ts +60 -0
  52. package/dist/components/builder/node-title.js +135 -0
  53. package/dist/components/builder/node-title.js.map +1 -0
  54. package/dist/components/builder/page-tree.js +25 -5
  55. package/dist/components/builder/page-tree.js.map +1 -1
  56. package/dist/components/builder/prop-selection.d.ts +52 -0
  57. package/dist/components/builder/prop-selection.js +38 -0
  58. package/dist/components/builder/prop-selection.js.map +1 -0
  59. package/dist/components/builder/prop-sync-context.d.ts +49 -0
  60. package/dist/components/builder/prop-sync-context.js +23 -0
  61. package/dist/components/builder/prop-sync-context.js.map +1 -0
  62. package/dist/components/builder/property-inspector.js +24 -1
  63. package/dist/components/builder/property-inspector.js.map +1 -1
  64. package/dist/components/builder/reuse-context.js +1 -1
  65. package/dist/components/builder/reuse-context.js.map +1 -1
  66. package/dist/components/builder/selection-overlay.d.ts +79 -0
  67. package/dist/components/builder/selection-overlay.js +244 -0
  68. package/dist/components/builder/selection-overlay.js.map +1 -0
  69. package/dist/components/builder/spacing-handles.d.ts +57 -1
  70. package/dist/components/builder/spacing-handles.js +142 -26
  71. package/dist/components/builder/spacing-handles.js.map +1 -1
  72. package/dist/components/cms/asset-field.js +21 -5
  73. package/dist/components/cms/asset-field.js.map +1 -1
  74. package/dist/components/cms/asset-picker.js +11 -5
  75. package/dist/components/cms/asset-picker.js.map +1 -1
  76. package/dist/components/cms/bulk-actions.d.ts +22 -3
  77. package/dist/components/cms/bulk-actions.js +27 -3
  78. package/dist/components/cms/bulk-actions.js.map +1 -1
  79. package/dist/components/cms/cms-store.js +19 -0
  80. package/dist/components/cms/cms-store.js.map +1 -1
  81. package/dist/components/cms/detail-editor.js +18 -19
  82. package/dist/components/cms/detail-editor.js.map +1 -1
  83. package/dist/components/cms/prop-fields.js +23 -16
  84. package/dist/components/cms/prop-fields.js.map +1 -1
  85. package/dist/components/cms/publish-clean.d.ts +21 -0
  86. package/dist/components/cms/publish-clean.js +30 -0
  87. package/dist/components/cms/publish-clean.js.map +1 -0
  88. package/dist/components/cms/rich-text/block-style.d.ts +48 -0
  89. package/dist/components/cms/rich-text/block-style.js +30 -0
  90. package/dist/components/cms/rich-text/block-style.js.map +1 -0
  91. package/dist/components/cms/rich-text/embed-picker.js +4 -3
  92. package/dist/components/cms/rich-text/embed-picker.js.map +1 -1
  93. package/dist/components/cms/rich-text/extensions.d.ts +2 -0
  94. package/dist/components/cms/rich-text/extensions.js +4 -0
  95. package/dist/components/cms/rich-text/extensions.js.map +1 -1
  96. package/dist/components/cms/rich-text/font-size-control.d.ts +31 -0
  97. package/dist/components/cms/rich-text/font-size-control.js +40 -0
  98. package/dist/components/cms/rich-text/font-size-control.js.map +1 -0
  99. package/dist/components/cms/rich-text/font-size.d.ts +16 -0
  100. package/dist/components/cms/rich-text/font-size.js +40 -0
  101. package/dist/components/cms/rich-text/font-size.js.map +1 -0
  102. package/dist/components/cms/rich-text/toolbar-state.d.ts +3 -0
  103. package/dist/components/cms/rich-text/toolbar-state.js +3 -0
  104. package/dist/components/cms/rich-text/toolbar-state.js.map +1 -1
  105. package/dist/components/cms/rich-text/toolbar.d.ts +13 -0
  106. package/dist/components/cms/rich-text/toolbar.js +17 -5
  107. package/dist/components/cms/rich-text/toolbar.js.map +1 -1
  108. package/dist/components/cms/use-live-url.d.ts +24 -1
  109. package/dist/components/cms/use-live-url.js +26 -4
  110. package/dist/components/cms/use-live-url.js.map +1 -1
  111. package/dist/components/cms/use-publish-controller.js +8 -6
  112. package/dist/components/cms/use-publish-controller.js.map +1 -1
  113. package/dist/components/collection-view/cms-collection-view-source.js +15 -7
  114. package/dist/components/collection-view/cms-collection-view-source.js.map +1 -1
  115. package/dist/components/components/component-detail.js +3 -3
  116. package/dist/components/components/component-detail.js.map +1 -1
  117. package/dist/components/components/component-preview.js +1 -1
  118. package/dist/components/components/component-preview.js.map +1 -1
  119. package/dist/components/components/registry-browser.js +2 -2
  120. package/dist/components/components/registry-browser.js.map +1 -1
  121. package/dist/components/components/taxonomy.js +3 -2
  122. package/dist/components/components/taxonomy.js.map +1 -1
  123. package/dist/components/components/transfer-dialog.js +1 -1
  124. package/dist/components/components/transfer-dialog.js.map +1 -1
  125. package/dist/lib/assets/backfill-thumbs.d.ts +67 -0
  126. package/dist/lib/assets/backfill-thumbs.js +120 -0
  127. package/dist/lib/assets/backfill-thumbs.js.map +1 -0
  128. package/dist/lib/assets/delete-asset.d.ts +2 -1
  129. package/dist/lib/assets/delete-asset.js +12 -0
  130. package/dist/lib/assets/delete-asset.js.map +1 -1
  131. package/dist/lib/assets/index.d.ts +2 -0
  132. package/dist/lib/assets/index.js +2 -0
  133. package/dist/lib/assets/index.js.map +1 -1
  134. package/dist/lib/assets/thumbnail.d.ts +145 -0
  135. package/dist/lib/assets/thumbnail.js +220 -0
  136. package/dist/lib/assets/thumbnail.js.map +1 -0
  137. package/dist/lib/assets/upload.d.ts +9 -0
  138. package/dist/lib/assets/upload.js +31 -0
  139. package/dist/lib/assets/upload.js.map +1 -1
  140. package/dist/lib/auth/auth-adapter.d.ts +22 -0
  141. package/dist/lib/auth/auth-adapter.js.map +1 -1
  142. package/dist/lib/auth/convex-auth-adapter.d.ts +42 -1
  143. package/dist/lib/auth/convex-auth-adapter.js +69 -0
  144. package/dist/lib/auth/convex-auth-adapter.js.map +1 -1
  145. package/dist/lib/auth/dev-auto-login-adapter.d.ts +2 -1
  146. package/dist/lib/auth/dev-auto-login-adapter.js +6 -0
  147. package/dist/lib/auth/dev-auto-login-adapter.js.map +1 -1
  148. package/dist/lib/auth/index.d.ts +1 -1
  149. package/dist/lib/auth/index.js.map +1 -1
  150. package/dist/lib/auth/local-auth-adapter.d.ts +2 -1
  151. package/dist/lib/auth/local-auth-adapter.js +7 -0
  152. package/dist/lib/auth/local-auth-adapter.js.map +1 -1
  153. package/dist/lib/auth-errors.d.ts +45 -3
  154. package/dist/lib/auth-errors.js +46 -5
  155. package/dist/lib/auth-errors.js.map +1 -1
  156. package/dist/lib/clipboard/paste-target.js +5 -2
  157. package/dist/lib/clipboard/paste-target.js.map +1 -1
  158. package/dist/lib/dnd/drop-rules.d.ts +13 -0
  159. package/dist/lib/dnd/drop-rules.js +15 -0
  160. package/dist/lib/dnd/drop-rules.js.map +1 -1
  161. package/dist/lib/i18n/messages.de.js +19 -0
  162. package/dist/lib/i18n/messages.de.js.map +1 -1
  163. package/dist/lib/i18n/messages.en.d.ts +16 -0
  164. package/dist/lib/i18n/messages.en.js +24 -0
  165. package/dist/lib/i18n/messages.en.js.map +1 -1
  166. package/dist/lib/persistence/convex-adapter.js +4 -0
  167. package/dist/lib/persistence/convex-adapter.js.map +1 -1
  168. package/dist/lib/persistence/index.js +6 -3
  169. package/dist/lib/persistence/index.js.map +1 -1
  170. package/dist/lib/persona/labels.d.ts +9 -5
  171. package/dist/lib/persona/labels.js +13 -8
  172. package/dist/lib/persona/labels.js.map +1 -1
  173. package/dist/lib/references/references-store.js +7 -1
  174. package/dist/lib/references/references-store.js.map +1 -1
  175. package/dist/lib/session-expiry.d.ts +19 -6
  176. package/dist/lib/session-expiry.js +13 -1
  177. package/dist/lib/session-expiry.js.map +1 -1
  178. package/dist/lib/session-revalidation.d.ts +81 -0
  179. package/dist/lib/session-revalidation.js +94 -0
  180. package/dist/lib/session-revalidation.js.map +1 -0
  181. package/package.json +2 -2
  182. package/src/styles/studio.css +13 -0
@@ -1 +1 @@
1
- {"version":3,"file":"convex-auth-adapter.js","sourceRoot":"","sources":["../../../src/lib/auth/convex-auth-adapter.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ConvexHttpClient } from 'convex/browser'\nimport { actionRef, mutationRef, queryRef } from '../persistence/convex-client'\nimport type {\n AuthAdapter,\n AuthSession,\n ChangePasswordInput,\n ChangePasswordResult,\n SignInCredentials,\n SignInResult,\n UpdateProfileInput,\n UpdateProfileResult,\n} from './auth-adapter'\nimport type { SessionStore } from './local-auth-adapter'\n\n/**\n * Convex Auth implementation of the project-owned {@link AuthAdapter}\n * (EC-150). Decision record (also in `convex/auth.ts`): **Convex Auth** over\n * Clerk — self-contained on the Convex stack, sessions in our own tables, no\n * extra vendor; reachable only through this adapter so a swap stays local.\n *\n * Flow (server side lives in `convex/auth.ts` + `convex/users.ts`):\n * 1. `signIn` calls the `auth:signIn` action with the Password provider\n * (`flow: 'signIn'`); on success Convex returns a JWT + refresh token.\n * 2. The JWT is set on the *shared* Convex client (`client.setAuth`), so every\n * subsequent persistence query/mutation runs authenticated and the\n * server-side `requireRole` guards see the caller.\n * 3. `currentSession` restores the JWT from storage and asks `users:me`.\n * 4. `signOut` calls `auth:signOut` and clears the JWT.\n *\n * v1 scope note: the stored JWT is short-lived and this adapter does not yet\n * run the refresh-token loop (the official `@convex-dev/auth/react` client\n * does); an expired session simply resolves to \"signed out\" and the user signs\n * in again. Live-deployment hardening is EC-156. No live deployment exists in\n * this repo — this file is typecheck-verified and exercised against the local\n * adapter's shared interface.\n */\n\nconst TOKEN_STORAGE_PREFIX = 'elytra.convex-auth.jwt'\n\n/**\n * Classify a thrown sign-in error as a DEPLOYMENT-setup failure rather than a\n * credential problem (EC-257). Matches the Convex Auth signing-path throws:\n * `requireEnv('JWT_PRIVATE_KEY'|'JWKS'|'CONVEX_SITE_URL')` (→ \"Missing environment\n * variable …\") and `importPKCS8` on a malformed key — plus the EC-363 server code\n * `deployment-misconfigured` (`defineStudioAuth` wraps those library throws in a\n * `ConvexError` whose `.data` survives prod redaction, so this classifies on\n * PRODUCTION deployments too once their functions are redeployed). Deliberately\n * narrow — a genuine wrong-password error carries none of these tokens — and checked\n * BEFORE the credential regex so it always wins. On a prod deployment whose\n * functions PREDATE EC-363 the plain-`Error` message is redacted to \"Server Error\",\n * which this can't match; there the honest fallback is the neutral \"unavailable\"\n * branch (never \"password too short\").\n */\nexport function isDeploymentMisconfigured(message: string): boolean {\n return /JWT_PRIVATE_KEY|JWKS|CONVEX_SITE_URL|Missing environment variable|PKCS8|private key|deployment-misconfigured/i.test(\n message,\n )\n}\n\n/**\n * Reduce a thrown Convex error to the text used for classification (EC-362).\n *\n * **Read `.data` FIRST, then `.message`** — because a PRODUCTION Convex\n * deployment REDACTS a UDF's thrown-error `.message` to a generic\n * `\"[Request ID: …] Server Error\"`, but a `ConvexError`'s `.data` payload\n * survives to the client. Proven in the installed `convex@1.42.1` browser\n * client: on an `\"error\"` response `ConvexHttpClient.action` throws\n * `forwardErrorData(respJSON.errorData, new ConvexError(respJSON.errorMessage))`,\n * and `forwardErrorData` sets `error.data = jsonToConvex(errorData)`\n * (`dist/esm/browser/http_client.js`, ~L388-396 + L459-461). The server keeps\n * `errorData` for a `ConvexError` in prod while redacting `errorMessage`. So the\n * only classifiable text that survives prod is the `.data` of a `ConvexError` —\n * our invite gate's `ConvexError(NOT_INVITED_REASON)` and, since EC-363, the\n * coded `ConvexError({ code })` that `defineStudioAuth` wraps around the\n * library's credential/env throws. On DEV deployments nothing is redacted, so\n * `.message` also carries the token (the regex ladder still matches);\n * non-Convex errors only have a `.message`.\n *\n * Duck-types the `data` property rather than `instanceof ConvexError`: two\n * copies of the `convex` module can coexist under Vite (transformed vs\n * pre-bundled), so `instanceof` can spuriously fail (EC-357). `.data` may be a\n * string (our gates) or a structured object (future gates) — both are folded in.\n */\nexport function convexErrorClassifierText(error: unknown): string {\n const parts: string[] = []\n if (typeof error === 'object' && error !== null && 'data' in error) {\n const data = (error as { data: unknown }).data\n if (typeof data === 'string') {\n parts.push(data)\n } else if (data !== null && data !== undefined) {\n try {\n parts.push(JSON.stringify(data))\n } catch {\n // Non-serializable data (cycles) — ignore; fall back to the message.\n }\n }\n }\n if (error instanceof Error) parts.push(error.message)\n else if (typeof error === 'string') parts.push(error)\n if (parts.length === 0) parts.push(String(error))\n return parts.join('\\n')\n}\n\ntype SignInFailure = Extract<SignInResult, { ok: false }>\n\n/**\n * Pure sign-in/sign-up error classifier (EC-257 / EC-324 / EC-362 / EC-363).\n * Runs the regex ladder over {@link convexErrorClassifierText} output\n * (data-first).\n *\n * EC-363: `defineStudioAuth` now wraps `@convex-dev/auth`'s plain-`Error`\n * credential/env throws server-side in a `ConvexError` carrying a stable code\n * in `.data` (`{\"code\":\"invalid-credentials\" | \"too-many-attempts\" |\n * \"account-exists\" | \"deployment-misconfigured\"}`). The data-first text folds\n * that JSON in, so the SAME ladder classifies the codes (they survive prod\n * redaction) AND the raw dev-shape messages — a deployment whose functions\n * predate EC-363 still classifies exactly as before (dev: full message; prod:\n * the honest generic below).\n *\n * Order matters: a DEPLOYMENT-misconfiguration (EC-257) and an uninvited\n * sign-up (EC-324) are each checked BEFORE the credential regex so neither\n * degrades to the lying \"password too short\". The final fallback is HONEST\n * (\"unexpected server error — check the deployment logs\"), never \"unavailable\":\n * unknown errors — and any plain `Error` a pre-EC-363 prod deployment redacts\n * to `\"Server Error\"` with no `.data` — legitimately land here.\n */\nexport function classifySignInError(flow: 'signIn' | 'signUp', text: string): SignInFailure {\n // EC-257: a DEPLOYMENT-misconfiguration — its auth signing key is unset — must be\n // classified FIRST so it never falls through to the lying \"password too short\".\n // Convex Auth signs the session JWT with the deployment's `JWT_PRIVATE_KEY`\n // (+ `JWKS`/`CONVEX_SITE_URL`); when any is unset (`npx @convex-dev/auth` never\n // run) the `requireEnv`/`importPKCS8` call throws AFTER the password is validated,\n // so EVERY sign-in fails regardless of the password.\n if (isDeploymentMisconfigured(text)) {\n return {\n ok: false,\n reason: 'deployment-misconfigured',\n message:\n 'This deployment can’t sign you in: its auth signing key is not set. Run `npx @convex-dev/auth` against this deployment, then retry. (The deployment-setup check reports the same.)',\n }\n }\n // EC-324: an uninvited sign-up is rejected server-side (`gateAccountCreation`/\n // `gateSessionCreation` throw `ConvexError(NOT_INVITED_REASON)`, whose `.data`\n // survives to the client even in production). Checked BEFORE the credential regex.\n if (/not-invited|invite-only|invite only/i.test(text)) {\n return {\n ok: false,\n reason: 'not-invited',\n message:\n 'This deployment is invite-only. Ask an admin to invite your email, then create your account.',\n }\n }\n // EC-363: the sign-in rate limit. New deployments code it server-side\n // (`{\"code\":\"too-many-attempts\"}` survives prod redaction); dev/old\n // deployments surface the library's plain `TooManyFailedAttempts`. Checked\n // BEFORE the credential regex; truthful wait-and-retry copy on the existing\n // `unavailable` reason (a throttled deployment \"can't sign you in right\n // now\" — the adapter contract gains no new reason for a transient state).\n if (/too-many-attempts|TooManyFailedAttempts/i.test(text)) {\n return {\n ok: false,\n reason: 'unavailable',\n message: 'Too many failed sign-in attempts — wait a moment, then try again.',\n }\n }\n // The EC-363 code `account-exists` matches via `exists`.\n if (flow === 'signUp' && /already|exists|InvalidAccountId/i.test(text)) {\n return {\n ok: false,\n reason: 'account-exists',\n message: 'An account with this email already exists — sign in instead.',\n }\n }\n // The EC-363 code `invalid-credentials` matches via `credential`.\n if (/InvalidSecret|InvalidAccountId|credential|password/i.test(text)) {\n return {\n ok: false,\n reason: 'invalid-credentials',\n message:\n flow === 'signUp'\n ? 'That email or password can’t be used (password too short?).'\n : 'Email or password is incorrect.',\n }\n }\n const verb = flow === 'signUp' ? 'Sign-up' : 'Sign-in'\n return {\n ok: false,\n reason: 'unavailable',\n message: `${verb} failed (unexpected server error) — check the deployment logs.`,\n }\n}\n\ntype ChangePasswordFailure = Extract<ChangePasswordResult, { ok: false }>\n\n/**\n * Pure password-change error classifier (EC-240 H-tail / EC-362 / EC-377). Same\n * data-first text + honest fallback discipline as {@link classifySignInError};\n * `deployment-misconfigured` collapses onto the `unavailable` reason\n * (`ChangePasswordResult` has no dedicated reason for it) with a specific\n * remediation message.\n *\n * EC-377: `users:changePassword` now codes its provider throws server-side with\n * the EC-363 `toCodedSignInError` wrap (`{\"code\":\"invalid-credentials\" |\n * \"too-many-attempts\" | \"deployment-misconfigured\"}`) plus a coded\n * `weak-password` min-length guard — the data-first text folds those in, so the\n * SAME ladder classifies the codes (they survive prod redaction) AND the raw\n * dev-shape messages. A deployment whose functions predate EC-377 classifies\n * exactly as before (dev: full message; prod: the honest generic below).\n */\nexport function classifyChangePasswordError(text: string): ChangePasswordFailure {\n // The EC-363/EC-377 code `deployment-misconfigured` matches via\n // `isDeploymentMisconfigured` (it carries the code token).\n if (isDeploymentMisconfigured(text)) {\n return {\n ok: false,\n reason: 'unavailable',\n message:\n 'This deployment can’t change your password: its auth signing key is not set. Run `npx @convex-dev/auth`.',\n }\n }\n // EC-377: `retrieveAccount` rate-limits failed CURRENT-password attempts like\n // sign-ins. New deployments code it (`{\"code\":\"too-many-attempts\"}` survives\n // prod redaction); dev/old deployments surface the library's plain\n // `TooManyFailedAttempts`. Checked BEFORE the credential regex; truthful\n // wait-and-retry copy on the existing `unavailable` reason (mirrors\n // `classifySignInError`, EC-363).\n if (/too-many-attempts|TooManyFailedAttempts/i.test(text)) {\n return {\n ok: false,\n reason: 'unavailable',\n message: 'Too many failed attempts — wait a moment, then try again.',\n }\n }\n // `invalid-credentials` is the EC-377 code token; the rest is the pre-EC-377\n // dev-message ladder, verbatim.\n if (/invalid-credentials|InvalidSecret|InvalidAccountId|current password|incorrect/i.test(text)) {\n return {\n ok: false,\n reason: 'invalid-credentials',\n message: 'Your current password is incorrect.',\n }\n }\n // The EC-377 code `weak-password` matches via `weak`.\n if (/at least|too short|8 characters|password requirement|weak/i.test(text)) {\n return {\n ok: false,\n reason: 'weak-password',\n message: 'Choose a stronger password (at least 8 characters).',\n }\n }\n return {\n ok: false,\n reason: 'unavailable',\n message: 'Password change failed (unexpected server error) — check the deployment logs.',\n }\n}\n\n/**\n * Per-deployment token key (EC-179, vision AD-8). One project per backend, so a\n * session is scoped to its deployment: signing into Aurora's backend must not\n * carry a JWT to Northwind's. Keying the stored token by the deployment URL\n * makes each backend's session independent — switching to a project on a\n * not-yet-authenticated backend reads no token and the guard asks for sign-in.\n * (A future shared external IdP — WorkOS/Clerk — would issue one identity valid\n * on every deployment, collapsing this back to a single session.)\n */\nexport function tokenStorageKey(deploymentUrl?: string): string {\n return deploymentUrl ? `${TOKEN_STORAGE_PREFIX}::${deploymentUrl}` : TOKEN_STORAGE_PREFIX\n}\n\n/**\n * Best-effort \"is there a session for this deployment?\" — a synchronous token\n * presence check used to label per-backend projects in the switcher/launcher\n * (EC-179). It does not validate the token (an expired one still redirects to\n * sign-in on entry); it just answers \"has the user signed into this backend?\".\n */\nexport function hasStoredConvexSession(\n deploymentUrl: string,\n store: SessionStore = defaultStore(),\n): boolean {\n return store.getItem(tokenStorageKey(deploymentUrl)) !== null\n}\n\ninterface SignInActionResult {\n tokens?: { token: string; refreshToken: string } | null\n}\n\ninterface MeResult {\n id: string\n email: string\n name?: string\n}\n\nfunction defaultStore(): SessionStore {\n if (typeof window !== 'undefined' && window.sessionStorage) return window.sessionStorage\n const memory = new Map<string, string>()\n return {\n getItem: (key) => memory.get(key) ?? null,\n setItem: (key, value) => void memory.set(key, value),\n removeItem: (key) => void memory.delete(key),\n }\n}\n\nexport class ConvexAuthAdapter implements AuthAdapter {\n private readonly tokenKey: string\n\n constructor(\n private readonly client: ConvexHttpClient,\n private readonly store: SessionStore = defaultStore(),\n /** Deployment URL the token is scoped to (per-backend sessions, EC-179). */\n deploymentUrl?: string,\n ) {\n this.tokenKey = tokenStorageKey(deploymentUrl)\n }\n\n async currentSession(): Promise<AuthSession | null> {\n const token = this.store.getItem(this.tokenKey)\n if (!token) return null\n this.client.setAuth(token)\n try {\n const me = (await this.client.query(queryRef('users:me'), {})) as MeResult | null\n if (!me) {\n this.clearToken()\n return null\n }\n return { user: this.toUser(me) }\n } catch {\n // Expired/invalid JWT or unreachable backend → treat as signed out.\n this.clearToken()\n return null\n }\n }\n\n async signIn(credentials: SignInCredentials): Promise<SignInResult> {\n return this.authenticate('signIn', credentials)\n }\n\n async signUp(credentials: SignInCredentials): Promise<SignInResult> {\n return this.authenticate('signUp', credentials)\n }\n\n /**\n * Shared password flow (EC-150/EC-179). `signIn` and `signUp` differ only by\n * the Convex Auth `flow` param; both return a token + load `users:me`.\n */\n private async authenticate(\n flow: 'signIn' | 'signUp',\n credentials: SignInCredentials,\n ): Promise<SignInResult> {\n let result: SignInActionResult\n try {\n result = (await this.client.action(actionRef('auth:signIn'), {\n provider: 'password',\n params: {\n email: credentials.email.trim().toLowerCase(),\n password: credentials.password,\n flow,\n },\n })) as SignInActionResult\n } catch (error) {\n // Convex Auth surfaces credential/registration problems as thrown errors.\n // Classify from the DATA-FIRST text (a `ConvexError`'s `.data` survives prod\n // redaction, the redacted `.message` does not — EC-362), then apply the pure\n // regex ladder. See `convexErrorClassifierText` / `classifySignInError`.\n return classifySignInError(flow, convexErrorClassifierText(error))\n }\n if (!result.tokens) {\n return {\n ok: false,\n reason: 'invalid-credentials',\n message: 'Email or password is incorrect.',\n }\n }\n this.store.setItem(this.tokenKey, result.tokens.token)\n this.client.setAuth(result.tokens.token)\n const me = (await this.client.query(queryRef('users:me'), {})) as MeResult | null\n if (!me) {\n this.clearToken()\n return { ok: false, reason: 'unavailable', message: 'Sign-in is currently unavailable.' }\n }\n return { ok: true, session: { user: this.toUser(me) } }\n }\n\n async signOut(): Promise<void> {\n try {\n await this.client.action(actionRef('auth:signOut'), {})\n } catch {\n // Already signed out / unreachable — clearing local state is what matters.\n }\n this.clearToken()\n }\n\n /**\n * Self-service identity edit (EC-240 H-tail item 2). Patches the caller's OWN\n * auth `users` row (name / avatar) via the self-scoped `users:updateProfile`\n * mutation, then re-reads `users:me` so the returned session carries the new\n * name. Auth-owned fields never flow through the members-rail `users.save`.\n */\n async updateProfile(input: UpdateProfileInput): Promise<UpdateProfileResult> {\n const token = this.store.getItem(this.tokenKey)\n if (!token) {\n return { ok: false, reason: 'not-authenticated', message: 'You are not signed in.' }\n }\n this.client.setAuth(token)\n try {\n await this.client.mutation(mutationRef('users:updateProfile'), {\n ...(input.name !== undefined ? { name: input.name } : {}),\n ...(input.avatar !== undefined ? { avatar: input.avatar } : {}),\n })\n const me = (await this.client.query(queryRef('users:me'), {})) as MeResult | null\n if (!me) {\n return { ok: false, reason: 'unavailable', message: 'Profile update is currently unavailable.' }\n }\n return { ok: true, session: { user: this.toUser(me) } }\n } catch {\n return { ok: false, reason: 'unavailable', message: 'Profile update is currently unavailable.' }\n }\n }\n\n /**\n * Self-service password change (EC-240 H-tail item 3). Delegates entirely to\n * the provider via the self-scoped `users:changePassword` ACTION (which calls\n * Convex Auth's `retrieveAccount` to verify the current password, then\n * `modifyAccountCredentials` to set the new one). **Seam only:** the raw\n * password values are forwarded to the provider and NEVER logged or inspected\n * here.\n */\n async changePassword(input: ChangePasswordInput): Promise<ChangePasswordResult> {\n const token = this.store.getItem(this.tokenKey)\n if (!token) {\n return { ok: false, reason: 'not-authenticated', message: 'You are not signed in.' }\n }\n this.client.setAuth(token)\n try {\n await this.client.action(actionRef('users:changePassword'), {\n currentPassword: input.currentPassword,\n newPassword: input.newPassword,\n })\n return { ok: true }\n } catch (error) {\n // Data-first classification (EC-362): the change-password ACTION runs on the\n // same deployment, so a prod redaction erases the thrown `.message` the same\n // way — read a surviving `ConvexError.data` first.\n return classifyChangePasswordError(convexErrorClassifierText(error))\n }\n }\n\n private toUser(me: MeResult): AuthSession['user'] {\n return {\n id: me.id,\n email: me.email,\n ...(me.name ? { name: me.name } : {}),\n // No global flags: project creation is config/CLI-owned (AD-11), so there\n // is no in-studio `create-projects` capability. Per-project roles come\n // from `projectMembers`.\n globalFlags: [],\n }\n }\n\n private clearToken(): void {\n this.store.removeItem(this.tokenKey)\n this.client.clearAuth()\n }\n}\n"]}
1
+ {"version":3,"file":"convex-auth-adapter.js","sourceRoot":"","sources":["../../../src/lib/auth/convex-auth-adapter.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ConvexHttpClient } from 'convex/browser'\nimport { actionRef, mutationRef, queryRef } from '../persistence/convex-client'\nimport type {\n AuthAdapter,\n AuthSession,\n ChangePasswordInput,\n ChangePasswordResult,\n SessionProbeResult,\n SignInCredentials,\n SignInResult,\n UpdateProfileInput,\n UpdateProfileResult,\n} from './auth-adapter'\nimport type { SessionStore } from './local-auth-adapter'\n\n/**\n * Convex Auth implementation of the project-owned {@link AuthAdapter}\n * (EC-150). Decision record (also in `convex/auth.ts`): **Convex Auth** over\n * Clerk — self-contained on the Convex stack, sessions in our own tables, no\n * extra vendor; reachable only through this adapter so a swap stays local.\n *\n * Flow (server side lives in `convex/auth.ts` + `convex/users.ts`):\n * 1. `signIn` calls the `auth:signIn` action with the Password provider\n * (`flow: 'signIn'`); on success Convex returns a JWT + refresh token.\n * 2. The JWT is set on the *shared* Convex client (`client.setAuth`), so every\n * subsequent persistence query/mutation runs authenticated and the\n * server-side `requireRole` guards see the caller.\n * 3. `currentSession` restores the JWT from storage and asks `users:me`.\n * 4. `signOut` calls `auth:signOut` and clears the JWT.\n *\n * v1 scope note: the stored JWT is short-lived and this adapter does not yet\n * run the refresh-token loop (the official `@convex-dev/auth/react` client\n * does); an expired session simply resolves to \"signed out\" and the user signs\n * in again. Live-deployment hardening is EC-156. No live deployment exists in\n * this repo — this file is typecheck-verified and exercised against the local\n * adapter's shared interface.\n */\n\nconst TOKEN_STORAGE_PREFIX = 'elytra.convex-auth.jwt'\n\n/**\n * Classify a thrown sign-in error as a DEPLOYMENT-setup failure rather than a\n * credential problem (EC-257). Matches the Convex Auth signing-path throws:\n * `requireEnv('JWT_PRIVATE_KEY'|'JWKS'|'CONVEX_SITE_URL')` (→ \"Missing environment\n * variable …\") and `importPKCS8` on a malformed key — plus the EC-363 server code\n * `deployment-misconfigured` (`defineStudioAuth` wraps those library throws in a\n * `ConvexError` whose `.data` survives prod redaction, so this classifies on\n * PRODUCTION deployments too once their functions are redeployed). Deliberately\n * narrow — a genuine wrong-password error carries none of these tokens — and checked\n * BEFORE the credential regex so it always wins. On a prod deployment whose\n * functions PREDATE EC-363 the plain-`Error` message is redacted to \"Server Error\",\n * which this can't match; there the honest fallback is the neutral \"unavailable\"\n * branch (never \"password too short\").\n */\nexport function isDeploymentMisconfigured(message: string): boolean {\n return /JWT_PRIVATE_KEY|JWKS|CONVEX_SITE_URL|Missing environment variable|PKCS8|private key|deployment-misconfigured/i.test(\n message,\n )\n}\n\n/**\n * Reduce a thrown Convex error to the text used for classification (EC-362).\n *\n * **Read `.data` FIRST, then `.message`** — because a PRODUCTION Convex\n * deployment REDACTS a UDF's thrown-error `.message` to a generic\n * `\"[Request ID: …] Server Error\"`, but a `ConvexError`'s `.data` payload\n * survives to the client. Proven in the installed `convex@1.42.1` browser\n * client: on an `\"error\"` response `ConvexHttpClient.action` throws\n * `forwardErrorData(respJSON.errorData, new ConvexError(respJSON.errorMessage))`,\n * and `forwardErrorData` sets `error.data = jsonToConvex(errorData)`\n * (`dist/esm/browser/http_client.js`, ~L388-396 + L459-461). The server keeps\n * `errorData` for a `ConvexError` in prod while redacting `errorMessage`. So the\n * only classifiable text that survives prod is the `.data` of a `ConvexError` —\n * our invite gate's `ConvexError(NOT_INVITED_REASON)` and, since EC-363, the\n * coded `ConvexError({ code })` that `defineStudioAuth` wraps around the\n * library's credential/env throws. On DEV deployments nothing is redacted, so\n * `.message` also carries the token (the regex ladder still matches);\n * non-Convex errors only have a `.message`.\n *\n * Duck-types the `data` property rather than `instanceof ConvexError`: two\n * copies of the `convex` module can coexist under Vite (transformed vs\n * pre-bundled), so `instanceof` can spuriously fail (EC-357). `.data` may be a\n * string (our gates) or a structured object (future gates) — both are folded in.\n */\nexport function convexErrorClassifierText(error: unknown): string {\n const parts: string[] = []\n if (typeof error === 'object' && error !== null && 'data' in error) {\n const data = (error as { data: unknown }).data\n if (typeof data === 'string') {\n parts.push(data)\n } else if (data !== null && data !== undefined) {\n try {\n parts.push(JSON.stringify(data))\n } catch {\n // Non-serializable data (cycles) — ignore; fall back to the message.\n }\n }\n }\n if (error instanceof Error) parts.push(error.message)\n else if (typeof error === 'string') parts.push(error)\n if (parts.length === 0) parts.push(String(error))\n return parts.join('\\n')\n}\n\n/**\n * Does this transport-error text carry Convex's POSITIVE \"the token was\n * rejected\" signal? (EC-415 probe classification.)\n *\n * Derivation (drift-proof, from the real transport): a rejected JWT on\n * `/api/query` makes the Convex backend answer HTTP 401 with a raw blob the\n * installed `convex@1.42.1` `ConvexHttpClient` re-throws VERBATIM as\n * `new Error(await response.text())` (`dist/esm/browser/http_client.js`,\n * `queryInner`: `if (!response.ok && response.status !== STATUS_CODE_UDF_FAILED)\n * throw new Error(await response.text())`). Two rejection codes exist, both\n * observed against a REAL deployment:\n *\n * - `{\"code\":\"Unauthenticated\",\"message\":\"Could not verify OIDC token claim: …\"}`\n * — an EXPIRED/revoked (but well-signed) session. The SAME markers\n * `lib/auth-errors.ts#isSessionExpiredError` pins for the reactive path\n * (EC-403).\n * - `{\"code\":\"InvalidAuthHeader\",\"message\":\"Could not parse JWT payload…\"}` /\n * `\"Could not parse as OIDC ID token…\"` — a malformed or unverifiable token\n * (captured LIVE from a convex.cloud deployment during EC-415 verification).\n * Equally authoritative: the backend answered and positively rejected the\n * credential; keeping such a token would just wedge every future probe.\n *\n * Deliberately narrow: an UNREACHABLE backend never produces this text — there\n * `fetch` itself rejects (a `TypeError`: \"Failed to fetch\" in Chromium,\n * \"Load failed\" in WebKit, \"fetch failed\" in Node/undici) and the client\n * propagates that rejection unwrapped. Anything not positively matched here is\n * treated as `unreachable`, which can never clear a valid token.\n */\nexport function isUnauthenticatedTransportText(text: string): boolean {\n return (\n /\\bUnauthenticated\\b/.test(text) ||\n /\\bInvalidAuthHeader\\b/.test(text) ||\n /could not verify oidc token/i.test(text)\n )\n}\n\ntype SignInFailure = Extract<SignInResult, { ok: false }>\n\n/**\n * Pure sign-in/sign-up error classifier (EC-257 / EC-324 / EC-362 / EC-363).\n * Runs the regex ladder over {@link convexErrorClassifierText} output\n * (data-first).\n *\n * EC-363: `defineStudioAuth` now wraps `@convex-dev/auth`'s plain-`Error`\n * credential/env throws server-side in a `ConvexError` carrying a stable code\n * in `.data` (`{\"code\":\"invalid-credentials\" | \"too-many-attempts\" |\n * \"account-exists\" | \"deployment-misconfigured\"}`). The data-first text folds\n * that JSON in, so the SAME ladder classifies the codes (they survive prod\n * redaction) AND the raw dev-shape messages — a deployment whose functions\n * predate EC-363 still classifies exactly as before (dev: full message; prod:\n * the honest generic below).\n *\n * Order matters: a DEPLOYMENT-misconfiguration (EC-257) and an uninvited\n * sign-up (EC-324) are each checked BEFORE the credential regex so neither\n * degrades to the lying \"password too short\". The final fallback is HONEST\n * (\"unexpected server error — check the deployment logs\"), never \"unavailable\":\n * unknown errors — and any plain `Error` a pre-EC-363 prod deployment redacts\n * to `\"Server Error\"` with no `.data` — legitimately land here.\n */\nexport function classifySignInError(flow: 'signIn' | 'signUp', text: string): SignInFailure {\n // EC-257: a DEPLOYMENT-misconfiguration — its auth signing key is unset — must be\n // classified FIRST so it never falls through to the lying \"password too short\".\n // Convex Auth signs the session JWT with the deployment's `JWT_PRIVATE_KEY`\n // (+ `JWKS`/`CONVEX_SITE_URL`); when any is unset (`npx @convex-dev/auth` never\n // run) the `requireEnv`/`importPKCS8` call throws AFTER the password is validated,\n // so EVERY sign-in fails regardless of the password.\n if (isDeploymentMisconfigured(text)) {\n return {\n ok: false,\n reason: 'deployment-misconfigured',\n message:\n 'This deployment can’t sign you in: its auth signing key is not set. Run `npx @convex-dev/auth` against this deployment, then retry. (The deployment-setup check reports the same.)',\n }\n }\n // EC-324: an uninvited sign-up is rejected server-side (`gateAccountCreation`/\n // `gateSessionCreation` throw `ConvexError(NOT_INVITED_REASON)`, whose `.data`\n // survives to the client even in production). Checked BEFORE the credential regex.\n if (/not-invited|invite-only|invite only/i.test(text)) {\n return {\n ok: false,\n reason: 'not-invited',\n message:\n 'This deployment is invite-only. Ask an admin to invite your email, then create your account.',\n }\n }\n // EC-363: the sign-in rate limit. New deployments code it server-side\n // (`{\"code\":\"too-many-attempts\"}` survives prod redaction); dev/old\n // deployments surface the library's plain `TooManyFailedAttempts`. Checked\n // BEFORE the credential regex; truthful wait-and-retry copy on the existing\n // `unavailable` reason (a throttled deployment \"can't sign you in right\n // now\" — the adapter contract gains no new reason for a transient state).\n if (/too-many-attempts|TooManyFailedAttempts/i.test(text)) {\n return {\n ok: false,\n reason: 'unavailable',\n message: 'Too many failed sign-in attempts — wait a moment, then try again.',\n }\n }\n // The EC-363 code `account-exists` matches via `exists`.\n if (flow === 'signUp' && /already|exists|InvalidAccountId/i.test(text)) {\n return {\n ok: false,\n reason: 'account-exists',\n message: 'An account with this email already exists — sign in instead.',\n }\n }\n // The EC-363 code `invalid-credentials` matches via `credential`.\n if (/InvalidSecret|InvalidAccountId|credential|password/i.test(text)) {\n return {\n ok: false,\n reason: 'invalid-credentials',\n message:\n flow === 'signUp'\n ? 'That email or password can’t be used (password too short?).'\n : 'Email or password is incorrect.',\n }\n }\n const verb = flow === 'signUp' ? 'Sign-up' : 'Sign-in'\n return {\n ok: false,\n reason: 'unavailable',\n message: `${verb} failed (unexpected server error) — check the deployment logs.`,\n }\n}\n\ntype ChangePasswordFailure = Extract<ChangePasswordResult, { ok: false }>\n\n/**\n * Pure password-change error classifier (EC-240 H-tail / EC-362 / EC-377). Same\n * data-first text + honest fallback discipline as {@link classifySignInError};\n * `deployment-misconfigured` collapses onto the `unavailable` reason\n * (`ChangePasswordResult` has no dedicated reason for it) with a specific\n * remediation message.\n *\n * EC-377: `users:changePassword` now codes its provider throws server-side with\n * the EC-363 `toCodedSignInError` wrap (`{\"code\":\"invalid-credentials\" |\n * \"too-many-attempts\" | \"deployment-misconfigured\"}`) plus a coded\n * `weak-password` min-length guard — the data-first text folds those in, so the\n * SAME ladder classifies the codes (they survive prod redaction) AND the raw\n * dev-shape messages. A deployment whose functions predate EC-377 classifies\n * exactly as before (dev: full message; prod: the honest generic below).\n */\nexport function classifyChangePasswordError(text: string): ChangePasswordFailure {\n // The EC-363/EC-377 code `deployment-misconfigured` matches via\n // `isDeploymentMisconfigured` (it carries the code token).\n if (isDeploymentMisconfigured(text)) {\n return {\n ok: false,\n reason: 'unavailable',\n message:\n 'This deployment can’t change your password: its auth signing key is not set. Run `npx @convex-dev/auth`.',\n }\n }\n // EC-377: `retrieveAccount` rate-limits failed CURRENT-password attempts like\n // sign-ins. New deployments code it (`{\"code\":\"too-many-attempts\"}` survives\n // prod redaction); dev/old deployments surface the library's plain\n // `TooManyFailedAttempts`. Checked BEFORE the credential regex; truthful\n // wait-and-retry copy on the existing `unavailable` reason (mirrors\n // `classifySignInError`, EC-363).\n if (/too-many-attempts|TooManyFailedAttempts/i.test(text)) {\n return {\n ok: false,\n reason: 'unavailable',\n message: 'Too many failed attempts — wait a moment, then try again.',\n }\n }\n // `invalid-credentials` is the EC-377 code token; the rest is the pre-EC-377\n // dev-message ladder, verbatim.\n if (/invalid-credentials|InvalidSecret|InvalidAccountId|current password|incorrect/i.test(text)) {\n return {\n ok: false,\n reason: 'invalid-credentials',\n message: 'Your current password is incorrect.',\n }\n }\n // The EC-377 code `weak-password` matches via `weak`.\n if (/at least|too short|8 characters|password requirement|weak/i.test(text)) {\n return {\n ok: false,\n reason: 'weak-password',\n message: 'Choose a stronger password (at least 8 characters).',\n }\n }\n return {\n ok: false,\n reason: 'unavailable',\n message: 'Password change failed (unexpected server error) — check the deployment logs.',\n }\n}\n\n/**\n * Per-deployment token key (EC-179, vision AD-8). One project per backend, so a\n * session is scoped to its deployment: signing into Aurora's backend must not\n * carry a JWT to Northwind's. Keying the stored token by the deployment URL\n * makes each backend's session independent — switching to a project on a\n * not-yet-authenticated backend reads no token and the guard asks for sign-in.\n * (A future shared external IdP — WorkOS/Clerk — would issue one identity valid\n * on every deployment, collapsing this back to a single session.)\n */\nexport function tokenStorageKey(deploymentUrl?: string): string {\n return deploymentUrl ? `${TOKEN_STORAGE_PREFIX}::${deploymentUrl}` : TOKEN_STORAGE_PREFIX\n}\n\n/**\n * Best-effort \"is there a session for this deployment?\" — a synchronous token\n * presence check used to label per-backend projects in the switcher/launcher\n * (EC-179). It does not validate the token (an expired one still redirects to\n * sign-in on entry); it just answers \"has the user signed into this backend?\".\n */\nexport function hasStoredConvexSession(\n deploymentUrl: string,\n store: SessionStore = defaultStore(),\n): boolean {\n return store.getItem(tokenStorageKey(deploymentUrl)) !== null\n}\n\ninterface SignInActionResult {\n tokens?: { token: string; refreshToken: string } | null\n}\n\ninterface MeResult {\n id: string\n email: string\n name?: string\n}\n\nfunction defaultStore(): SessionStore {\n if (typeof window !== 'undefined' && window.sessionStorage) return window.sessionStorage\n const memory = new Map<string, string>()\n return {\n getItem: (key) => memory.get(key) ?? null,\n setItem: (key, value) => void memory.set(key, value),\n removeItem: (key) => void memory.delete(key),\n }\n}\n\nexport class ConvexAuthAdapter implements AuthAdapter {\n private readonly tokenKey: string\n\n constructor(\n private readonly client: ConvexHttpClient,\n private readonly store: SessionStore = defaultStore(),\n /** Deployment URL the token is scoped to (per-backend sessions, EC-179). */\n deploymentUrl?: string,\n ) {\n this.tokenKey = tokenStorageKey(deploymentUrl)\n }\n\n async currentSession(): Promise<AuthSession | null> {\n const token = this.store.getItem(this.tokenKey)\n if (!token) return null\n this.client.setAuth(token)\n try {\n const me = (await this.client.query(queryRef('users:me'), {})) as MeResult | null\n if (!me) {\n this.clearToken()\n return null\n }\n return { user: this.toUser(me) }\n } catch {\n // Expired/invalid JWT or unreachable backend → treat as signed out.\n this.clearToken()\n return null\n }\n }\n\n /**\n * Tri-state session probe (EC-415). Same `users:me` round-trip as\n * {@link currentSession}, but WITHOUT its destructive catch-all: only a\n * POSITIVE token rejection from the backend ({@link\n * isUnauthenticatedTransportText} over the data-first error text) — or the\n * backend answering \"no identity\" (`users:me` → `null`, e.g. the server-side\n * session row was deleted) — clears the stored token. Every other failure\n * (fetch rejection, 5xx, unclassifiable) is `unreachable`: the token survives\n * so a network blip can never sign an editor out. `currentSession()` keeps\n * its existing external behavior for its callers; never throws.\n */\n async probeSession(): Promise<SessionProbeResult> {\n const token = this.store.getItem(this.tokenKey)\n if (!token) return 'invalid'\n this.client.setAuth(token)\n try {\n const me = (await this.client.query(queryRef('users:me'), {})) as MeResult | null\n if (!me) {\n // The backend answered: the token verified but no identity exists\n // (session revoked/deleted server-side) — the session is dead.\n this.clearToken()\n return 'invalid'\n }\n return 'valid'\n } catch (error) {\n if (isUnauthenticatedTransportText(convexErrorClassifierText(error))) {\n this.clearToken()\n return 'invalid'\n }\n // No authoritative answer — NEVER clear the token here (the EC-403\n // finding's exact hazard: a focus poll signing editors out on a blip).\n return 'unreachable'\n }\n }\n\n async signIn(credentials: SignInCredentials): Promise<SignInResult> {\n return this.authenticate('signIn', credentials)\n }\n\n async signUp(credentials: SignInCredentials): Promise<SignInResult> {\n return this.authenticate('signUp', credentials)\n }\n\n /**\n * Shared password flow (EC-150/EC-179). `signIn` and `signUp` differ only by\n * the Convex Auth `flow` param; both return a token + load `users:me`.\n */\n private async authenticate(\n flow: 'signIn' | 'signUp',\n credentials: SignInCredentials,\n ): Promise<SignInResult> {\n let result: SignInActionResult\n try {\n result = (await this.client.action(actionRef('auth:signIn'), {\n provider: 'password',\n params: {\n email: credentials.email.trim().toLowerCase(),\n password: credentials.password,\n flow,\n },\n })) as SignInActionResult\n } catch (error) {\n // Convex Auth surfaces credential/registration problems as thrown errors.\n // Classify from the DATA-FIRST text (a `ConvexError`'s `.data` survives prod\n // redaction, the redacted `.message` does not — EC-362), then apply the pure\n // regex ladder. See `convexErrorClassifierText` / `classifySignInError`.\n return classifySignInError(flow, convexErrorClassifierText(error))\n }\n if (!result.tokens) {\n return {\n ok: false,\n reason: 'invalid-credentials',\n message: 'Email or password is incorrect.',\n }\n }\n this.store.setItem(this.tokenKey, result.tokens.token)\n this.client.setAuth(result.tokens.token)\n const me = (await this.client.query(queryRef('users:me'), {})) as MeResult | null\n if (!me) {\n this.clearToken()\n return { ok: false, reason: 'unavailable', message: 'Sign-in is currently unavailable.' }\n }\n return { ok: true, session: { user: this.toUser(me) } }\n }\n\n async signOut(): Promise<void> {\n try {\n await this.client.action(actionRef('auth:signOut'), {})\n } catch {\n // Already signed out / unreachable — clearing local state is what matters.\n }\n this.clearToken()\n }\n\n /**\n * Self-service identity edit (EC-240 H-tail item 2). Patches the caller's OWN\n * auth `users` row (name / avatar) via the self-scoped `users:updateProfile`\n * mutation, then re-reads `users:me` so the returned session carries the new\n * name. Auth-owned fields never flow through the members-rail `users.save`.\n */\n async updateProfile(input: UpdateProfileInput): Promise<UpdateProfileResult> {\n const token = this.store.getItem(this.tokenKey)\n if (!token) {\n return { ok: false, reason: 'not-authenticated', message: 'You are not signed in.' }\n }\n this.client.setAuth(token)\n try {\n await this.client.mutation(mutationRef('users:updateProfile'), {\n ...(input.name !== undefined ? { name: input.name } : {}),\n ...(input.avatar !== undefined ? { avatar: input.avatar } : {}),\n })\n const me = (await this.client.query(queryRef('users:me'), {})) as MeResult | null\n if (!me) {\n return { ok: false, reason: 'unavailable', message: 'Profile update is currently unavailable.' }\n }\n return { ok: true, session: { user: this.toUser(me) } }\n } catch {\n return { ok: false, reason: 'unavailable', message: 'Profile update is currently unavailable.' }\n }\n }\n\n /**\n * Self-service password change (EC-240 H-tail item 3). Delegates entirely to\n * the provider via the self-scoped `users:changePassword` ACTION (which calls\n * Convex Auth's `retrieveAccount` to verify the current password, then\n * `modifyAccountCredentials` to set the new one). **Seam only:** the raw\n * password values are forwarded to the provider and NEVER logged or inspected\n * here.\n */\n async changePassword(input: ChangePasswordInput): Promise<ChangePasswordResult> {\n const token = this.store.getItem(this.tokenKey)\n if (!token) {\n return { ok: false, reason: 'not-authenticated', message: 'You are not signed in.' }\n }\n this.client.setAuth(token)\n try {\n await this.client.action(actionRef('users:changePassword'), {\n currentPassword: input.currentPassword,\n newPassword: input.newPassword,\n })\n return { ok: true }\n } catch (error) {\n // Data-first classification (EC-362): the change-password ACTION runs on the\n // same deployment, so a prod redaction erases the thrown `.message` the same\n // way — read a surviving `ConvexError.data` first.\n return classifyChangePasswordError(convexErrorClassifierText(error))\n }\n }\n\n private toUser(me: MeResult): AuthSession['user'] {\n return {\n id: me.id,\n email: me.email,\n ...(me.name ? { name: me.name } : {}),\n // No global flags: project creation is config/CLI-owned (AD-11), so there\n // is no in-studio `create-projects` capability. Per-project roles come\n // from `projectMembers`.\n globalFlags: [],\n }\n }\n\n private clearToken(): void {\n this.store.removeItem(this.tokenKey)\n this.client.clearAuth()\n }\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { AuthAdapter, AuthSession, ChangePasswordInput, ChangePasswordResult, SignInCredentials, SignInResult, UpdateProfileInput, UpdateProfileResult } from './auth-adapter.js';
1
+ import type { AuthAdapter, AuthSession, ChangePasswordInput, ChangePasswordResult, SessionProbeResult, SignInCredentials, SignInResult, UpdateProfileInput, UpdateProfileResult } from './auth-adapter.js';
2
2
  /**
3
3
  * Dev-only unattended-auth decorator (testing de-friction). Wraps any
4
4
  * {@link AuthAdapter}: when no session can be restored, it transparently signs
@@ -19,6 +19,7 @@ export declare class DevAutoLoginAdapter implements AuthAdapter {
19
19
  private readonly credentials;
20
20
  constructor(inner: AuthAdapter, credentials: SignInCredentials);
21
21
  currentSession(): Promise<AuthSession | null>;
22
+ probeSession(): Promise<SessionProbeResult>;
22
23
  signIn(credentials: SignInCredentials): Promise<SignInResult>;
23
24
  signUp(credentials: SignInCredentials): Promise<SignInResult>;
24
25
  signOut(): Promise<void>;
@@ -27,6 +27,12 @@ export class DevAutoLoginAdapter {
27
27
  const result = await this.inner.signIn(this.credentials);
28
28
  return result.ok ? result.session : null;
29
29
  }
30
+ probeSession() {
31
+ // A probe is a probe — no auto-sign-in here. If the session died, the
32
+ // blocking re-auth overlay (EC-403) is the correct surface; a reload's
33
+ // `currentSession` restore is where the auto-login convenience lives.
34
+ return this.inner.probeSession();
35
+ }
30
36
  signIn(credentials) {
31
37
  return this.inner.signIn(credentials);
32
38
  }
@@ -1 +1 @@
1
- {"version":3,"file":"dev-auto-login-adapter.js","sourceRoot":"","sources":["../../../src/lib/auth/dev-auto-login-adapter.ts"],"names":[],"mappings":"AAWA;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,mBAAmB;IAEX;IACA;IAFnB,YACmB,KAAkB,EAClB,WAA8B;QAD9B,UAAK,GAAL,KAAK,CAAa;QAClB,gBAAW,GAAX,WAAW,CAAmB;IAC9C,CAAC;IAEJ,KAAK,CAAC,cAAc;QAClB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,CAAA;QAClD,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAA;QAC7B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QACxD,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAA;IAC1C,CAAC;IAED,MAAM,CAAC,WAA8B;QACnC,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;IACvC,CAAC;IAED,MAAM,CAAC,WAA8B;QACnC,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;IACvC,CAAC;IAED,OAAO;QACL,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAA;IAC7B,CAAC;IAED,aAAa,CAAC,KAAyB;QACrC,OAAO,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;IACxC,CAAC;IAED,cAAc,CAAC,KAA0B;QACvC,OAAO,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,CAAA;IACzC,CAAC;CACF","sourcesContent":["import type {\n AuthAdapter,\n AuthSession,\n ChangePasswordInput,\n ChangePasswordResult,\n SignInCredentials,\n SignInResult,\n UpdateProfileInput,\n UpdateProfileResult,\n} from './auth-adapter'\n\n/**\n * Dev-only unattended-auth decorator (testing de-friction). Wraps any\n * {@link AuthAdapter}: when no session can be restored, it transparently signs\n * in with the configured dev credentials so the studio boots already\n * authenticated for local/automated testing.\n *\n * This is **not** an auth bypass — it performs a real {@link AuthAdapter.signIn},\n * so the resulting session is a genuine backend session and the server-side\n * role guards still apply (a non-member or wrong password surfaces as the normal\n * signed-out state, not silent access). It is only ever constructed in a dev\n * build (see `resolveDevAutoLogin`), so it cannot reach production.\n *\n * Every method but {@link currentSession} delegates unchanged; `currentSession`\n * adds the auto-sign-in fallback after the wrapped adapter reports no session.\n */\nexport class DevAutoLoginAdapter implements AuthAdapter {\n constructor(\n private readonly inner: AuthAdapter,\n private readonly credentials: SignInCredentials,\n ) {}\n\n async currentSession(): Promise<AuthSession | null> {\n const restored = await this.inner.currentSession()\n if (restored) return restored\n const result = await this.inner.signIn(this.credentials)\n return result.ok ? result.session : null\n }\n\n signIn(credentials: SignInCredentials): Promise<SignInResult> {\n return this.inner.signIn(credentials)\n }\n\n signUp(credentials: SignInCredentials): Promise<SignInResult> {\n return this.inner.signUp(credentials)\n }\n\n signOut(): Promise<void> {\n return this.inner.signOut()\n }\n\n updateProfile(input: UpdateProfileInput): Promise<UpdateProfileResult> {\n return this.inner.updateProfile(input)\n }\n\n changePassword(input: ChangePasswordInput): Promise<ChangePasswordResult> {\n return this.inner.changePassword(input)\n }\n}\n"]}
1
+ {"version":3,"file":"dev-auto-login-adapter.js","sourceRoot":"","sources":["../../../src/lib/auth/dev-auto-login-adapter.ts"],"names":[],"mappings":"AAYA;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,mBAAmB;IAEX;IACA;IAFnB,YACmB,KAAkB,EAClB,WAA8B;QAD9B,UAAK,GAAL,KAAK,CAAa;QAClB,gBAAW,GAAX,WAAW,CAAmB;IAC9C,CAAC;IAEJ,KAAK,CAAC,cAAc;QAClB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,CAAA;QAClD,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAA;QAC7B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QACxD,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAA;IAC1C,CAAC;IAED,YAAY;QACV,sEAAsE;QACtE,uEAAuE;QACvE,sEAAsE;QACtE,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,EAAE,CAAA;IAClC,CAAC;IAED,MAAM,CAAC,WAA8B;QACnC,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;IACvC,CAAC;IAED,MAAM,CAAC,WAA8B;QACnC,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;IACvC,CAAC;IAED,OAAO;QACL,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAA;IAC7B,CAAC;IAED,aAAa,CAAC,KAAyB;QACrC,OAAO,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;IACxC,CAAC;IAED,cAAc,CAAC,KAA0B;QACvC,OAAO,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,CAAA;IACzC,CAAC;CACF","sourcesContent":["import type {\n AuthAdapter,\n AuthSession,\n ChangePasswordInput,\n ChangePasswordResult,\n SessionProbeResult,\n SignInCredentials,\n SignInResult,\n UpdateProfileInput,\n UpdateProfileResult,\n} from './auth-adapter'\n\n/**\n * Dev-only unattended-auth decorator (testing de-friction). Wraps any\n * {@link AuthAdapter}: when no session can be restored, it transparently signs\n * in with the configured dev credentials so the studio boots already\n * authenticated for local/automated testing.\n *\n * This is **not** an auth bypass — it performs a real {@link AuthAdapter.signIn},\n * so the resulting session is a genuine backend session and the server-side\n * role guards still apply (a non-member or wrong password surfaces as the normal\n * signed-out state, not silent access). It is only ever constructed in a dev\n * build (see `resolveDevAutoLogin`), so it cannot reach production.\n *\n * Every method but {@link currentSession} delegates unchanged; `currentSession`\n * adds the auto-sign-in fallback after the wrapped adapter reports no session.\n */\nexport class DevAutoLoginAdapter implements AuthAdapter {\n constructor(\n private readonly inner: AuthAdapter,\n private readonly credentials: SignInCredentials,\n ) {}\n\n async currentSession(): Promise<AuthSession | null> {\n const restored = await this.inner.currentSession()\n if (restored) return restored\n const result = await this.inner.signIn(this.credentials)\n return result.ok ? result.session : null\n }\n\n probeSession(): Promise<SessionProbeResult> {\n // A probe is a probe — no auto-sign-in here. If the session died, the\n // blocking re-auth overlay (EC-403) is the correct surface; a reload's\n // `currentSession` restore is where the auto-login convenience lives.\n return this.inner.probeSession()\n }\n\n signIn(credentials: SignInCredentials): Promise<SignInResult> {\n return this.inner.signIn(credentials)\n }\n\n signUp(credentials: SignInCredentials): Promise<SignInResult> {\n return this.inner.signUp(credentials)\n }\n\n signOut(): Promise<void> {\n return this.inner.signOut()\n }\n\n updateProfile(input: UpdateProfileInput): Promise<UpdateProfileResult> {\n return this.inner.updateProfile(input)\n }\n\n changePassword(input: ChangePasswordInput): Promise<ChangePasswordResult> {\n return this.inner.changePassword(input)\n }\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import type { StudioBackendMode } from '../persistence/backend-mode.js';
2
2
  import type { AuthAdapter } from './auth-adapter.js';
3
- export type { AuthAdapter, AuthSession, AuthUser, ChangePasswordInput, ChangePasswordResult, SignInCredentials, SignInResult, UpdateProfileInput, UpdateProfileResult, } from './auth-adapter.js';
3
+ export type { AuthAdapter, AuthSession, AuthUser, ChangePasswordInput, ChangePasswordResult, SessionProbeResult, SignInCredentials, SignInResult, UpdateProfileInput, UpdateProfileResult, } from './auth-adapter.js';
4
4
  export { LocalAuthAdapter, LOCAL_DEV_CREDENTIALS } from './local-auth-adapter.js';
5
5
  export type { SessionStore } from './local-auth-adapter.js';
6
6
  export { ConvexAuthAdapter } from './convex-auth-adapter.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAA;AAC9D,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AACvF,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAGzE,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AACjF,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAavD,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AAE9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,eAAe,CAAA;AAGtB;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB;IAC/B,MAAM,MAAM,GAAG,oBAAoB,EAAE,CAAA;IACrC,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,2EAA2E;QAC3E,uEAAuE;QACvE,MAAM,GAAG,GAAG,oBAAoB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QAC5C,MAAM,IAAI,GAAG,IAAI,iBAAiB,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,CAAA;QACxE,OAAO,gBAAgB,CAAC,IAAI,CAAC,CAAA;IAC/B,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,KAAK,YAAY;QAAE,OAAO,gBAAgB,CAAC,IAAI,gBAAgB,EAAE,CAAC,CAAA;IACjF,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;AAChC,CAAC;AAED;;;;GAIG;AACH,SAAS,gBAAgB,CAAC,OAAoB;IAC5C,MAAM,WAAW,GAAG,mBAAmB,EAAE,CAAA;IACzC,OAAO,WAAW,CAAC,CAAC,CAAC,IAAI,mBAAmB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,OAAO,CAAA;AAC9E,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,QAAQ;IACtB,MAAM,MAAM,GAAG,oBAAoB,EAAE,CAAA;IACrC,IAAI,MAAM,CAAC,IAAI,KAAK,eAAe;QAAE,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IACnE,OAAO,MAAM,CAAC,IAAI,CAAA;AACpB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,MAAM,MAAM,GAAG,oBAAoB,EAAE,CAAA;IACrC,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IACzC,OAAO,sBAAsB,CAAC,oBAAoB,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;AACvE,CAAC","sourcesContent":["import { getConvexClient } from '../persistence/convex-client'\nimport { resolveBackendConfig, resolveDevAutoLogin } from '../persistence/backend-mode'\nimport { deploymentUrlForSlug, resolveDeploymentUrl } from '../workspace'\nimport type { StudioBackendMode } from '../persistence/backend-mode'\nimport type { AuthAdapter } from './auth-adapter'\nimport { ConvexAuthAdapter, hasStoredConvexSession } from './convex-auth-adapter'\nimport { DevAutoLoginAdapter } from './dev-auto-login-adapter'\nimport { LocalAuthAdapter } from './local-auth-adapter'\n\nexport type {\n AuthAdapter,\n AuthSession,\n AuthUser,\n ChangePasswordInput,\n ChangePasswordResult,\n SignInCredentials,\n SignInResult,\n UpdateProfileInput,\n UpdateProfileResult,\n} from './auth-adapter'\nexport { LocalAuthAdapter, LOCAL_DEV_CREDENTIALS } from './local-auth-adapter'\nexport type { SessionStore } from './local-auth-adapter'\nexport { ConvexAuthAdapter } from './convex-auth-adapter'\nexport { DevAutoLoginAdapter } from './dev-auto-login-adapter'\nexport {\n SIGN_IN_PATH,\n isPublicPath,\n postSignInTarget,\n resolveRouteGuard,\n sessionPrincipal,\n} from './route-guard'\nexport type { AuthStatus, RouteGuardDecision } from './route-guard'\n\n/**\n * Select the auth backend (EC-150/EC-153), mirroring `selectPersistenceAdapter`\n * exactly: Convex mode means Convex Auth (over the same shared client the\n * persistence adapter uses, so the session JWT authenticates every call); the\n * explicit playground mode means the deterministic local adapter with the\n * seeded dev users. A misconfigured backend is a hard error here too — never\n * a silent local fallback (the `DeploymentSetupGate` renders it before this runs).\n */\nexport function selectAuthAdapter(): AuthAdapter {\n const config = resolveBackendConfig()\n if (config.kind === 'convex') {\n // The active project (EC-179) selects the deployment; the session token is\n // scoped to that deployment URL so each backend keeps its own session.\n const url = resolveDeploymentUrl(config.url)\n const base = new ConvexAuthAdapter(getConvexClient(url), undefined, url)\n return withDevAutoLogin(base)\n }\n if (config.kind === 'playground') return withDevAutoLogin(new LocalAuthAdapter())\n throw new Error(config.detail)\n}\n\n/**\n * Dev-only: wrap an adapter so the studio auto-signs-in for unattended testing\n * when `VITE_ELYTRA_DEV_LOGIN` is configured in a dev build. No-op otherwise\n * (and dead-code-eliminated in production builds — see `resolveDevAutoLogin`).\n */\nfunction withDevAutoLogin(adapter: AuthAdapter): AuthAdapter {\n const credentials = resolveDevAutoLogin()\n return credentials ? new DevAutoLoginAdapter(adapter, credentials) : adapter\n}\n\n/** Mirrors `persistenceMode()` — auth and persistence always agree (EC-150). */\nexport function authMode(): StudioBackendMode {\n const config = resolveBackendConfig()\n if (config.kind === 'misconfigured') throw new Error(config.detail)\n return config.kind\n}\n\n/**\n * Whether the user has a session on a given project's backend (EC-179). One\n * project per backend, so this is the per-project sign-in state the switcher and\n * launcher surface (\"Sign in\" vs \"Open project\"). Playground mode is a single\n * local backend — always \"signed in\". Best-effort (token presence, see\n * {@link hasStoredConvexSession}); entry still enforces a valid session.\n */\nexport function hasProjectSession(slug: string): boolean {\n const config = resolveBackendConfig()\n if (config.kind !== 'convex') return true\n return hasStoredConvexSession(deploymentUrlForSlug(slug, config.url))\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAA;AAC9D,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AACvF,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAGzE,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AACjF,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAcvD,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AAE9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,eAAe,CAAA;AAGtB;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB;IAC/B,MAAM,MAAM,GAAG,oBAAoB,EAAE,CAAA;IACrC,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,2EAA2E;QAC3E,uEAAuE;QACvE,MAAM,GAAG,GAAG,oBAAoB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QAC5C,MAAM,IAAI,GAAG,IAAI,iBAAiB,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,CAAA;QACxE,OAAO,gBAAgB,CAAC,IAAI,CAAC,CAAA;IAC/B,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,KAAK,YAAY;QAAE,OAAO,gBAAgB,CAAC,IAAI,gBAAgB,EAAE,CAAC,CAAA;IACjF,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;AAChC,CAAC;AAED;;;;GAIG;AACH,SAAS,gBAAgB,CAAC,OAAoB;IAC5C,MAAM,WAAW,GAAG,mBAAmB,EAAE,CAAA;IACzC,OAAO,WAAW,CAAC,CAAC,CAAC,IAAI,mBAAmB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,OAAO,CAAA;AAC9E,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,QAAQ;IACtB,MAAM,MAAM,GAAG,oBAAoB,EAAE,CAAA;IACrC,IAAI,MAAM,CAAC,IAAI,KAAK,eAAe;QAAE,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IACnE,OAAO,MAAM,CAAC,IAAI,CAAA;AACpB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,MAAM,MAAM,GAAG,oBAAoB,EAAE,CAAA;IACrC,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IACzC,OAAO,sBAAsB,CAAC,oBAAoB,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;AACvE,CAAC","sourcesContent":["import { getConvexClient } from '../persistence/convex-client'\nimport { resolveBackendConfig, resolveDevAutoLogin } from '../persistence/backend-mode'\nimport { deploymentUrlForSlug, resolveDeploymentUrl } from '../workspace'\nimport type { StudioBackendMode } from '../persistence/backend-mode'\nimport type { AuthAdapter } from './auth-adapter'\nimport { ConvexAuthAdapter, hasStoredConvexSession } from './convex-auth-adapter'\nimport { DevAutoLoginAdapter } from './dev-auto-login-adapter'\nimport { LocalAuthAdapter } from './local-auth-adapter'\n\nexport type {\n AuthAdapter,\n AuthSession,\n AuthUser,\n ChangePasswordInput,\n ChangePasswordResult,\n SessionProbeResult,\n SignInCredentials,\n SignInResult,\n UpdateProfileInput,\n UpdateProfileResult,\n} from './auth-adapter'\nexport { LocalAuthAdapter, LOCAL_DEV_CREDENTIALS } from './local-auth-adapter'\nexport type { SessionStore } from './local-auth-adapter'\nexport { ConvexAuthAdapter } from './convex-auth-adapter'\nexport { DevAutoLoginAdapter } from './dev-auto-login-adapter'\nexport {\n SIGN_IN_PATH,\n isPublicPath,\n postSignInTarget,\n resolveRouteGuard,\n sessionPrincipal,\n} from './route-guard'\nexport type { AuthStatus, RouteGuardDecision } from './route-guard'\n\n/**\n * Select the auth backend (EC-150/EC-153), mirroring `selectPersistenceAdapter`\n * exactly: Convex mode means Convex Auth (over the same shared client the\n * persistence adapter uses, so the session JWT authenticates every call); the\n * explicit playground mode means the deterministic local adapter with the\n * seeded dev users. A misconfigured backend is a hard error here too — never\n * a silent local fallback (the `DeploymentSetupGate` renders it before this runs).\n */\nexport function selectAuthAdapter(): AuthAdapter {\n const config = resolveBackendConfig()\n if (config.kind === 'convex') {\n // The active project (EC-179) selects the deployment; the session token is\n // scoped to that deployment URL so each backend keeps its own session.\n const url = resolveDeploymentUrl(config.url)\n const base = new ConvexAuthAdapter(getConvexClient(url), undefined, url)\n return withDevAutoLogin(base)\n }\n if (config.kind === 'playground') return withDevAutoLogin(new LocalAuthAdapter())\n throw new Error(config.detail)\n}\n\n/**\n * Dev-only: wrap an adapter so the studio auto-signs-in for unattended testing\n * when `VITE_ELYTRA_DEV_LOGIN` is configured in a dev build. No-op otherwise\n * (and dead-code-eliminated in production builds — see `resolveDevAutoLogin`).\n */\nfunction withDevAutoLogin(adapter: AuthAdapter): AuthAdapter {\n const credentials = resolveDevAutoLogin()\n return credentials ? new DevAutoLoginAdapter(adapter, credentials) : adapter\n}\n\n/** Mirrors `persistenceMode()` — auth and persistence always agree (EC-150). */\nexport function authMode(): StudioBackendMode {\n const config = resolveBackendConfig()\n if (config.kind === 'misconfigured') throw new Error(config.detail)\n return config.kind\n}\n\n/**\n * Whether the user has a session on a given project's backend (EC-179). One\n * project per backend, so this is the per-project sign-in state the switcher and\n * launcher surface (\"Sign in\" vs \"Open project\"). Playground mode is a single\n * local backend — always \"signed in\". Best-effort (token presence, see\n * {@link hasStoredConvexSession}); entry still enforces a valid session.\n */\nexport function hasProjectSession(slug: string): boolean {\n const config = resolveBackendConfig()\n if (config.kind !== 'convex') return true\n return hasStoredConvexSession(deploymentUrlForSlug(slug, config.url))\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { AuthAdapter, AuthSession, ChangePasswordResult, SignInCredentials, SignInResult, UpdateProfileResult } from './auth-adapter.js';
1
+ import type { AuthAdapter, AuthSession, ChangePasswordResult, SessionProbeResult, SignInCredentials, SignInResult, UpdateProfileResult } from './auth-adapter.js';
2
2
  /**
3
3
  * Deterministic local auth (EC-150) for the in-memory persistence mode: the
4
4
  * whole sign-in → role-enforcement → sign-out flow works offline, in tests,
@@ -51,6 +51,7 @@ export declare class LocalAuthAdapter implements AuthAdapter {
51
51
  private readonly store;
52
52
  constructor(store?: SessionStore);
53
53
  currentSession(): Promise<AuthSession | null>;
54
+ probeSession(): Promise<SessionProbeResult>;
54
55
  signIn(credentials: SignInCredentials): Promise<SignInResult>;
55
56
  signUp(): Promise<SignInResult>;
56
57
  signOut(): Promise<void>;
@@ -47,6 +47,13 @@ export class LocalAuthAdapter {
47
47
  const entry = LOCAL_DEV_CREDENTIALS.find((c) => c.user.id === userId);
48
48
  return entry ? { user: entry.user } : null;
49
49
  }
50
+ async probeSession() {
51
+ // Playground sessions live entirely in local storage — there is no backend
52
+ // that could expire or reject them, so the probe is trivially `valid` and
53
+ // proactive re-validation is a no-op (EC-415 scope 4: byte-identical
54
+ // playground behavior).
55
+ return 'valid';
56
+ }
50
57
  async signIn(credentials) {
51
58
  const email = credentials.email.trim().toLowerCase();
52
59
  const entry = LOCAL_DEV_CREDENTIALS.find((c) => c.user.email === email);
@@ -1 +1 @@
1
- {"version":3,"file":"local-auth-adapter.js","sourceRoot":"","sources":["../../../src/lib/auth/local-auth-adapter.ts"],"names":[],"mappings":"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","sourcesContent":["import { DEV_USERS } from '@elytracms/core/persistence'\nimport type {\n AuthAdapter,\n AuthSession,\n ChangePasswordResult,\n SignInCredentials,\n SignInResult,\n UpdateProfileResult,\n} from './auth-adapter'\n\n/**\n * Deterministic local auth (EC-150) for the in-memory persistence mode: the\n * whole sign-in → role-enforcement → sign-out flow works offline, in tests,\n * and in fixtures with zero backend setup. User ids are the canonical\n * `DEV_USERS` ids from `@elytracms/core/persistence`, which the seeded\n * `projectMembers` fixtures reference — so `admin@example.com` is admin,\n * `editor@…` editor, `viewer@…` viewer on every sample project.\n *\n * Dev credentials (fixed, documented, local-only):\n * - admin@example.com / elytra-admin\n * - editor@example.com / elytra-editor\n * - viewer@example.com / elytra-viewer\n *\n * Adminness is the `projectMembers` role, not a global flag — project creation\n * is config/CLI-owned now (AD-11), so there is no in-studio `create-projects`\n * capability to grant.\n *\n * The session survives reloads via injected storage (defaults to\n * `sessionStorage` in the browser, an in-memory map elsewhere).\n */\n\nexport const LOCAL_DEV_CREDENTIALS = [\n { user: DEV_USERS.admin, password: 'elytra-admin' },\n { user: DEV_USERS.editor, password: 'elytra-editor' },\n { user: DEV_USERS.viewer, password: 'elytra-viewer' },\n] as const\n\nconst STORAGE_KEY = 'elytra.local-auth.user-id'\n\n/** The storage slice the adapter needs (sessionStorage-compatible). */\nexport interface SessionStore {\n getItem(key: string): string | null\n setItem(key: string, value: string): void\n removeItem(key: string): void\n}\n\nfunction defaultStore(): SessionStore {\n if (typeof window !== 'undefined' && window.sessionStorage) return window.sessionStorage\n const memory = new Map<string, string>()\n return {\n getItem: (key) => memory.get(key) ?? null,\n setItem: (key, value) => void memory.set(key, value),\n removeItem: (key) => void memory.delete(key),\n }\n}\n\nexport class LocalAuthAdapter implements AuthAdapter {\n private readonly store: SessionStore\n\n constructor(store: SessionStore = defaultStore()) {\n this.store = store\n }\n\n async currentSession(): Promise<AuthSession | null> {\n const userId = this.store.getItem(STORAGE_KEY)\n if (!userId) return null\n const entry = LOCAL_DEV_CREDENTIALS.find((c) => c.user.id === userId)\n return entry ? { user: entry.user } : null\n }\n\n async signIn(credentials: SignInCredentials): Promise<SignInResult> {\n const email = credentials.email.trim().toLowerCase()\n const entry = LOCAL_DEV_CREDENTIALS.find((c) => c.user.email === email)\n if (!entry || entry.password !== credentials.password) {\n // Deliberately identical for unknown email vs wrong password.\n return {\n ok: false,\n reason: 'invalid-credentials',\n message: 'Email or password is incorrect.',\n }\n }\n this.store.setItem(STORAGE_KEY, entry.user.id)\n return { ok: true, session: { user: entry.user } }\n }\n\n async signUp(): Promise<SignInResult> {\n // Playground mode has a fixed, documented set of dev users — there is no\n // registration. Surfaced as a state, not a throw, so the sign-in screen can\n // explain it.\n return {\n ok: false,\n reason: 'unavailable',\n message: 'Sign-up isn’t available in playground mode — use the dev credentials below.',\n }\n }\n\n async signOut(): Promise<void> {\n this.store.removeItem(STORAGE_KEY)\n }\n\n async updateProfile(): Promise<UpdateProfileResult> {\n // Playground users are fixed, documented fixtures — there is no persistence to\n // write. Surfaced as a state, not a throw, so the account surface can explain it.\n return {\n ok: false,\n reason: 'unsupported',\n message: 'Profile editing isn’t available in playground mode.',\n }\n }\n\n async changePassword(): Promise<ChangePasswordResult> {\n return {\n ok: false,\n reason: 'unsupported',\n message: 'Password change isn’t available in playground mode.',\n }\n }\n}\n"]}
1
+ {"version":3,"file":"local-auth-adapter.js","sourceRoot":"","sources":["../../../src/lib/auth/local-auth-adapter.ts"],"names":[],"mappings":"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","sourcesContent":["import { DEV_USERS } from '@elytracms/core/persistence'\nimport type {\n AuthAdapter,\n AuthSession,\n ChangePasswordResult,\n SessionProbeResult,\n SignInCredentials,\n SignInResult,\n UpdateProfileResult,\n} from './auth-adapter'\n\n/**\n * Deterministic local auth (EC-150) for the in-memory persistence mode: the\n * whole sign-in → role-enforcement → sign-out flow works offline, in tests,\n * and in fixtures with zero backend setup. User ids are the canonical\n * `DEV_USERS` ids from `@elytracms/core/persistence`, which the seeded\n * `projectMembers` fixtures reference — so `admin@example.com` is admin,\n * `editor@…` editor, `viewer@…` viewer on every sample project.\n *\n * Dev credentials (fixed, documented, local-only):\n * - admin@example.com / elytra-admin\n * - editor@example.com / elytra-editor\n * - viewer@example.com / elytra-viewer\n *\n * Adminness is the `projectMembers` role, not a global flag — project creation\n * is config/CLI-owned now (AD-11), so there is no in-studio `create-projects`\n * capability to grant.\n *\n * The session survives reloads via injected storage (defaults to\n * `sessionStorage` in the browser, an in-memory map elsewhere).\n */\n\nexport const LOCAL_DEV_CREDENTIALS = [\n { user: DEV_USERS.admin, password: 'elytra-admin' },\n { user: DEV_USERS.editor, password: 'elytra-editor' },\n { user: DEV_USERS.viewer, password: 'elytra-viewer' },\n] as const\n\nconst STORAGE_KEY = 'elytra.local-auth.user-id'\n\n/** The storage slice the adapter needs (sessionStorage-compatible). */\nexport interface SessionStore {\n getItem(key: string): string | null\n setItem(key: string, value: string): void\n removeItem(key: string): void\n}\n\nfunction defaultStore(): SessionStore {\n if (typeof window !== 'undefined' && window.sessionStorage) return window.sessionStorage\n const memory = new Map<string, string>()\n return {\n getItem: (key) => memory.get(key) ?? null,\n setItem: (key, value) => void memory.set(key, value),\n removeItem: (key) => void memory.delete(key),\n }\n}\n\nexport class LocalAuthAdapter implements AuthAdapter {\n private readonly store: SessionStore\n\n constructor(store: SessionStore = defaultStore()) {\n this.store = store\n }\n\n async currentSession(): Promise<AuthSession | null> {\n const userId = this.store.getItem(STORAGE_KEY)\n if (!userId) return null\n const entry = LOCAL_DEV_CREDENTIALS.find((c) => c.user.id === userId)\n return entry ? { user: entry.user } : null\n }\n\n async probeSession(): Promise<SessionProbeResult> {\n // Playground sessions live entirely in local storage — there is no backend\n // that could expire or reject them, so the probe is trivially `valid` and\n // proactive re-validation is a no-op (EC-415 scope 4: byte-identical\n // playground behavior).\n return 'valid'\n }\n\n async signIn(credentials: SignInCredentials): Promise<SignInResult> {\n const email = credentials.email.trim().toLowerCase()\n const entry = LOCAL_DEV_CREDENTIALS.find((c) => c.user.email === email)\n if (!entry || entry.password !== credentials.password) {\n // Deliberately identical for unknown email vs wrong password.\n return {\n ok: false,\n reason: 'invalid-credentials',\n message: 'Email or password is incorrect.',\n }\n }\n this.store.setItem(STORAGE_KEY, entry.user.id)\n return { ok: true, session: { user: entry.user } }\n }\n\n async signUp(): Promise<SignInResult> {\n // Playground mode has a fixed, documented set of dev users — there is no\n // registration. Surfaced as a state, not a throw, so the sign-in screen can\n // explain it.\n return {\n ok: false,\n reason: 'unavailable',\n message: 'Sign-up isn’t available in playground mode — use the dev credentials below.',\n }\n }\n\n async signOut(): Promise<void> {\n this.store.removeItem(STORAGE_KEY)\n }\n\n async updateProfile(): Promise<UpdateProfileResult> {\n // Playground users are fixed, documented fixtures — there is no persistence to\n // write. Surfaced as a state, not a throw, so the account surface can explain it.\n return {\n ok: false,\n reason: 'unsupported',\n message: 'Profile editing isn’t available in playground mode.',\n }\n }\n\n async changePassword(): Promise<ChangePasswordResult> {\n return {\n ok: false,\n reason: 'unsupported',\n message: 'Password change isn’t available in playground mode.',\n }\n }\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { ChangePasswordResult, SignInResult } from './auth/auth-adapter.js';
1
+ import type { ChangePasswordResult, SignInResult, UpdateProfileResult } from './auth/auth-adapter.js';
2
2
  import type { OperationError } from './operations/index.js';
3
3
  /**
4
4
  * Does this operation failure mean "the session is no longer valid"?
@@ -7,8 +7,17 @@ import type { OperationError } from './operations/index.js';
7
7
  * studio-level failures (`validation`, `unauthorized` from the role hook,
8
8
  * `not-found`, `conflict`) are never session expiry. Within those, detect the
9
9
  * Convex unauthenticated shape: an embedded `{"code":"Unauthenticated"}`
10
- * payload, the bare `Unauthenticated` server-error marker, or the OIDC claim
11
- * message it ships with.
10
+ * payload, or the SAME positive-rejection markers the EC-415 probe classifier
11
+ * ({@link isUnauthenticatedTransportText}, `lib/auth/convex-auth-adapter.ts`)
12
+ * pins — the bare `Unauthenticated` server-error marker, the OIDC claim
13
+ * message it ships with, and (EC-427) `InvalidAuthHeader`: the backend
14
+ * positively rejecting a malformed/unverifiable token, not just an
15
+ * expired-claims one. Reusing that one exported classifier (rather than a
16
+ * second copy of the regex ladder here) is the drift guard — the probe and
17
+ * this reactive predicate can never recognize a different set of real 401
18
+ * bodies (`lib/auth-errors.test.ts` proves agreement over every pinned
19
+ * fixture). `lib/auth/**` itself is untouched: the classifier was already
20
+ * exported for this reuse.
12
21
  */
13
22
  export declare function isSessionExpiredError(error: OperationError): boolean;
14
23
  type SignInFailure = Extract<SignInResult, {
@@ -69,4 +78,37 @@ export declare function changePasswordErrorMessageKey(failure: {
69
78
  reason: string;
70
79
  message: string;
71
80
  }): string | null;
81
+ type UpdateProfileFailure = Extract<UpdateProfileResult, {
82
+ ok: false;
83
+ }>;
84
+ /**
85
+ * Engine-owned update-profile-failure → message-catalog-key mapping (EC-416) —
86
+ * the {@link SIGN_IN_ERROR_KEYS} / {@link CHANGE_PASSWORD_ERROR_KEYS} design
87
+ * extended to the account identity form (`self-account-view-source.ts`'s
88
+ * `save`, which calls `AuthAdapter.updateProfile`). The reasons are the
89
+ * {@link UpdateProfileResult} failure union. Unlike change-password,
90
+ * `updateProfile` is NOT richly classified: the `users:updateProfile` Convex
91
+ * mutation never throws a coded `ConvexError`, so `ConvexAuthAdapter` collapses
92
+ * every failure (missing `users:me` row, thrown mutation error, network
93
+ * failure) into one generic `unavailable` message — there is no
94
+ * `classifyUpdateProfileError` to extend, and adding one would mean editing the
95
+ * no-touch `lib/auth/**` mutation to throw coded errors, which is out of scope
96
+ * here.
97
+ *
98
+ * `not-authenticated` reuses `account.password.error.notSignedIn` rather than
99
+ * a new key: `ConvexAuthAdapter.updateProfile` and `.changePassword` both
100
+ * return the byte-identical constant `'You are not signed in.'` for a missing
101
+ * token, so a second key would only duplicate the same English (and German)
102
+ * copy under a different name.
103
+ */
104
+ export declare const UPDATE_PROFILE_ERROR_KEYS: Record<UpdateProfileFailure['reason'], readonly string[]>;
105
+ /**
106
+ * Resolve a failed {@link UpdateProfileResult} to its message-catalog key, or
107
+ * `null` when the failure is not an engine-known shape (render
108
+ * `result.message` raw — the honest fallback).
109
+ */
110
+ export declare function updateProfileErrorMessageKey(failure: {
111
+ reason: string;
112
+ message: string;
113
+ }): string | null;
72
114
  export {};
@@ -1,3 +1,4 @@
1
+ import { isUnauthenticatedTransportText } from './auth/convex-auth-adapter.js';
1
2
  import { EN_MESSAGES } from './i18n/messages.en.js';
2
3
  /**
3
4
  * Session-expiry detection + plain-language mapping (EC-177 gap 9).
@@ -48,17 +49,24 @@ function embeddedJsonCode(message) {
48
49
  * studio-level failures (`validation`, `unauthorized` from the role hook,
49
50
  * `not-found`, `conflict`) are never session expiry. Within those, detect the
50
51
  * Convex unauthenticated shape: an embedded `{"code":"Unauthenticated"}`
51
- * payload, the bare `Unauthenticated` server-error marker, or the OIDC claim
52
- * message it ships with.
52
+ * payload, or the SAME positive-rejection markers the EC-415 probe classifier
53
+ * ({@link isUnauthenticatedTransportText}, `lib/auth/convex-auth-adapter.ts`)
54
+ * pins — the bare `Unauthenticated` server-error marker, the OIDC claim
55
+ * message it ships with, and (EC-427) `InvalidAuthHeader`: the backend
56
+ * positively rejecting a malformed/unverifiable token, not just an
57
+ * expired-claims one. Reusing that one exported classifier (rather than a
58
+ * second copy of the regex ladder here) is the drift guard — the probe and
59
+ * this reactive predicate can never recognize a different set of real 401
60
+ * bodies (`lib/auth-errors.test.ts` proves agreement over every pinned
61
+ * fixture). `lib/auth/**` itself is untouched: the classifier was already
62
+ * exported for this reuse.
53
63
  */
54
64
  export function isSessionExpiredError(error) {
55
65
  if (error.code !== 'unknown' && error.code !== 'backend-unavailable')
56
66
  return false;
57
67
  if (embeddedJsonCode(error.message) === 'Unauthenticated')
58
68
  return true;
59
- if (/\bUnauthenticated\b/.test(error.message))
60
- return true;
61
- return /could not verify oidc token/i.test(error.message);
69
+ return isUnauthenticatedTransportText(error.message);
62
70
  }
63
71
  /**
64
72
  * Engine-owned sign-in-failure → message-catalog-key mapping (EC-397). The
@@ -146,6 +154,39 @@ export const CHANGE_PASSWORD_ERROR_KEYS = {
146
154
  export function changePasswordErrorMessageKey(failure) {
147
155
  return errorMessageKey(CHANGE_PASSWORD_ERROR_KEYS, failure);
148
156
  }
157
+ /**
158
+ * Engine-owned update-profile-failure → message-catalog-key mapping (EC-416) —
159
+ * the {@link SIGN_IN_ERROR_KEYS} / {@link CHANGE_PASSWORD_ERROR_KEYS} design
160
+ * extended to the account identity form (`self-account-view-source.ts`'s
161
+ * `save`, which calls `AuthAdapter.updateProfile`). The reasons are the
162
+ * {@link UpdateProfileResult} failure union. Unlike change-password,
163
+ * `updateProfile` is NOT richly classified: the `users:updateProfile` Convex
164
+ * mutation never throws a coded `ConvexError`, so `ConvexAuthAdapter` collapses
165
+ * every failure (missing `users:me` row, thrown mutation error, network
166
+ * failure) into one generic `unavailable` message — there is no
167
+ * `classifyUpdateProfileError` to extend, and adding one would mean editing the
168
+ * no-touch `lib/auth/**` mutation to throw coded errors, which is out of scope
169
+ * here.
170
+ *
171
+ * `not-authenticated` reuses `account.password.error.notSignedIn` rather than
172
+ * a new key: `ConvexAuthAdapter.updateProfile` and `.changePassword` both
173
+ * return the byte-identical constant `'You are not signed in.'` for a missing
174
+ * token, so a second key would only duplicate the same English (and German)
175
+ * copy under a different name.
176
+ */
177
+ export const UPDATE_PROFILE_ERROR_KEYS = {
178
+ 'not-authenticated': ['account.password.error.notSignedIn'],
179
+ unavailable: ['account.profile.error.unavailable'],
180
+ unsupported: ['account.profile.error.playgroundUnsupported'],
181
+ };
182
+ /**
183
+ * Resolve a failed {@link UpdateProfileResult} to its message-catalog key, or
184
+ * `null` when the failure is not an engine-known shape (render
185
+ * `result.message` raw — the honest fallback).
186
+ */
187
+ export function updateProfileErrorMessageKey(failure) {
188
+ return errorMessageKey(UPDATE_PROFILE_ERROR_KEYS, failure);
189
+ }
149
190
  // `signInHref` (the banner's navigating "Sign in again" action) was retired by
150
191
  // EC-403: re-auth happens in place via the blocking overlay — a navigation
151
192
  // would destroy exactly the unsaved editor state the treatment protects.
@@ -1 +1 @@
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"]}
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, UpdateProfileResult } from './auth/auth-adapter'\nimport { isUnauthenticatedTransportText } from './auth/convex-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, or the SAME positive-rejection markers the EC-415 probe classifier\n * ({@link isUnauthenticatedTransportText}, `lib/auth/convex-auth-adapter.ts`)\n * pins — the bare `Unauthenticated` server-error marker, the OIDC claim\n * message it ships with, and (EC-427) `InvalidAuthHeader`: the backend\n * positively rejecting a malformed/unverifiable token, not just an\n * expired-claims one. Reusing that one exported classifier (rather than a\n * second copy of the regex ladder here) is the drift guard — the probe and\n * this reactive predicate can never recognize a different set of real 401\n * bodies (`lib/auth-errors.test.ts` proves agreement over every pinned\n * fixture). `lib/auth/**` itself is untouched: the classifier was already\n * exported for this reuse.\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 return isUnauthenticatedTransportText(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\ntype UpdateProfileFailure = Extract<UpdateProfileResult, { ok: false }>\n\n/**\n * Engine-owned update-profile-failure → message-catalog-key mapping (EC-416) —\n * the {@link SIGN_IN_ERROR_KEYS} / {@link CHANGE_PASSWORD_ERROR_KEYS} design\n * extended to the account identity form (`self-account-view-source.ts`'s\n * `save`, which calls `AuthAdapter.updateProfile`). The reasons are the\n * {@link UpdateProfileResult} failure union. Unlike change-password,\n * `updateProfile` is NOT richly classified: the `users:updateProfile` Convex\n * mutation never throws a coded `ConvexError`, so `ConvexAuthAdapter` collapses\n * every failure (missing `users:me` row, thrown mutation error, network\n * failure) into one generic `unavailable` message — there is no\n * `classifyUpdateProfileError` to extend, and adding one would mean editing the\n * no-touch `lib/auth/**` mutation to throw coded errors, which is out of scope\n * here.\n *\n * `not-authenticated` reuses `account.password.error.notSignedIn` rather than\n * a new key: `ConvexAuthAdapter.updateProfile` and `.changePassword` both\n * return the byte-identical constant `'You are not signed in.'` for a missing\n * token, so a second key would only duplicate the same English (and German)\n * copy under a different name.\n */\nexport const UPDATE_PROFILE_ERROR_KEYS: Record<UpdateProfileFailure['reason'], readonly string[]> = {\n 'not-authenticated': ['account.password.error.notSignedIn'],\n unavailable: ['account.profile.error.unavailable'],\n unsupported: ['account.profile.error.playgroundUnsupported'],\n}\n\n/**\n * Resolve a failed {@link UpdateProfileResult} 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 updateProfileErrorMessageKey(failure: {\n reason: string\n message: string\n}): string | null {\n return errorMessageKey(UPDATE_PROFILE_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"]}
@@ -1,12 +1,15 @@
1
1
  import { getNode, getParentInfo } from '@elytracms/core/project-graph';
2
+ import { manifestRules, primaryInsertSlot } from '@elytracms/studio/lib/dnd/drop-rules';
2
3
  import { shortId } from '@elytracms/studio/lib/persona/labels';
3
4
  export function resolvePasteTarget(args) {
4
5
  const { root, getManifest } = args;
5
6
  const anchor = args.anchorId !== null ? getNode(root, args.anchorId) : undefined;
6
7
  const node = anchor ?? root;
8
+ // EC-386: the shared slot rule (`primaryInsertSlot` over the merged
9
+ // `manifestRules` vocabulary) — a blocks-prop region (e.g. a Section's `body`)
10
+ // receives the paste exactly like the insert dialog and a DnD `into`.
7
11
  const manifest = getManifest(node.componentId);
8
- const childSlot = manifest?.slots.find((s) => s.name === 'children') ??
9
- manifest?.slots.find((s) => s.name !== 'outlet');
12
+ const childSlot = primaryInsertSlot(manifestRules(manifest));
10
13
  if (childSlot) {
11
14
  return {
12
15
  target: { parentId: node.id, slot: childSlot.name },
@@ -1 +1 @@
1
- {"version":3,"file":"paste-target.js","sourceRoot":"","sources":["../../../src/lib/clipboard/paste-target.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAGtE,OAAO,EAAE,OAAO,EAAE,MAAM,sCAAsC,CAAA;AAgB9D,MAAM,UAAU,kBAAkB,CAAC,IAKlC;IACC,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,IAAI,CAAA;IAClC,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IAChF,MAAM,IAAI,GAAG,MAAM,IAAI,IAAI,CAAA;IAE3B,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IAC9C,MAAM,SAAS,GACb,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC;QAClD,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAA;IAClD,IAAI,SAAS,EAAE,CAAC;QACd,OAAO;YACL,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE;YACnD,gBAAgB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,SAAS,CAAC,IAAI,EAAE;SACrE,CAAA;IACH,CAAC;IAED,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC,CAAA;IACzC,IAAI,IAAI,EAAE,CAAC;QACT,OAAO;YACL,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,GAAG,CAAC,EAAE;YAC5E,gBAAgB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE;SACvE,CAAA;IACH,CAAC;IAED,8EAA8E;IAC9E,OAAO,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,gBAAgB,EAAE,MAAM,EAAE,CAAA;AACtF,CAAC","sourcesContent":["import { getNode, getParentInfo } from '@elytracms/core/project-graph'\nimport type { ComponentNode, InsertTarget } from '@elytracms/core/project-graph'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\nimport { shortId } from '@elytracms/studio/lib/persona/labels'\n\n/**\n * Paste landing-spot resolution (EC-175) — the same placement rule the Insert\n * dialog uses (`insert-context.ts`): a pasted subtree lands *into*\n * the anchor node's child slot when it has one, else *after* the anchor as a\n * sibling, else appended to the page root. Pure and selection-driven so the\n * canvas, the page tree, and keyboard paste all share one rule.\n */\n\nexport interface PasteTarget {\n target: InsertTarget\n /** Human label of where the paste lands (notices/debugging). */\n destinationLabel: string\n}\n\nexport function resolvePasteTarget(args: {\n root: ComponentNode\n /** The anchor node (usually the selection); `null` falls back to the root. */\n anchorId: string | null\n getManifest: (componentId: string) => ComponentManifest | undefined\n}): PasteTarget {\n const { root, getManifest } = args\n const anchor = args.anchorId !== null ? getNode(root, args.anchorId) : undefined\n const node = anchor ?? root\n\n const manifest = getManifest(node.componentId)\n const childSlot =\n manifest?.slots.find((s) => s.name === 'children') ??\n manifest?.slots.find((s) => s.name !== 'outlet')\n if (childSlot) {\n return {\n target: { parentId: node.id, slot: childSlot.name },\n destinationLabel: `${shortId(node.componentId)} · ${childSlot.name}`,\n }\n }\n\n const info = getParentInfo(root, node.id)\n if (info) {\n return {\n target: { parentId: info.parent.id, slot: info.slot, index: info.index + 1 },\n destinationLabel: `${shortId(info.parent.componentId)} · ${info.slot}`,\n }\n }\n\n // Leaf root with no declared slots (degenerate graphs): append to `children`.\n return { target: { parentId: root.id, slot: 'children' }, destinationLabel: 'root' }\n}\n"]}
1
+ {"version":3,"file":"paste-target.js","sourceRoot":"","sources":["../../../src/lib/clipboard/paste-target.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAGtE,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAA;AACvF,OAAO,EAAE,OAAO,EAAE,MAAM,sCAAsC,CAAA;AAgB9D,MAAM,UAAU,kBAAkB,CAAC,IAKlC;IACC,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,IAAI,CAAA;IAClC,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IAChF,MAAM,IAAI,GAAG,MAAM,IAAI,IAAI,CAAA;IAE3B,oEAAoE;IACpE,+EAA+E;IAC/E,sEAAsE;IACtE,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IAC9C,MAAM,SAAS,GAAG,iBAAiB,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAA;IAC5D,IAAI,SAAS,EAAE,CAAC;QACd,OAAO;YACL,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE;YACnD,gBAAgB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,SAAS,CAAC,IAAI,EAAE;SACrE,CAAA;IACH,CAAC;IAED,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC,CAAA;IACzC,IAAI,IAAI,EAAE,CAAC;QACT,OAAO;YACL,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,GAAG,CAAC,EAAE;YAC5E,gBAAgB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE;SACvE,CAAA;IACH,CAAC;IAED,8EAA8E;IAC9E,OAAO,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,gBAAgB,EAAE,MAAM,EAAE,CAAA;AACtF,CAAC","sourcesContent":["import { getNode, getParentInfo } from '@elytracms/core/project-graph'\nimport type { ComponentNode, InsertTarget } from '@elytracms/core/project-graph'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\nimport { manifestRules, primaryInsertSlot } from '@elytracms/studio/lib/dnd/drop-rules'\nimport { shortId } from '@elytracms/studio/lib/persona/labels'\n\n/**\n * Paste landing-spot resolution (EC-175) — the same placement rule the Insert\n * dialog uses (`insert-context.ts`): a pasted subtree lands *into*\n * the anchor node's child slot when it has one, else *after* the anchor as a\n * sibling, else appended to the page root. Pure and selection-driven so the\n * canvas, the page tree, and keyboard paste all share one rule.\n */\n\nexport interface PasteTarget {\n target: InsertTarget\n /** Human label of where the paste lands (notices/debugging). */\n destinationLabel: string\n}\n\nexport function resolvePasteTarget(args: {\n root: ComponentNode\n /** The anchor node (usually the selection); `null` falls back to the root. */\n anchorId: string | null\n getManifest: (componentId: string) => ComponentManifest | undefined\n}): PasteTarget {\n const { root, getManifest } = args\n const anchor = args.anchorId !== null ? getNode(root, args.anchorId) : undefined\n const node = anchor ?? root\n\n // EC-386: the shared slot rule (`primaryInsertSlot` over the merged\n // `manifestRules` vocabulary) — a blocks-prop region (e.g. a Section's `body`)\n // receives the paste exactly like the insert dialog and a DnD `into`.\n const manifest = getManifest(node.componentId)\n const childSlot = primaryInsertSlot(manifestRules(manifest))\n if (childSlot) {\n return {\n target: { parentId: node.id, slot: childSlot.name },\n destinationLabel: `${shortId(node.componentId)} · ${childSlot.name}`,\n }\n }\n\n const info = getParentInfo(root, node.id)\n if (info) {\n return {\n target: { parentId: info.parent.id, slot: info.slot, index: info.index + 1 },\n destinationLabel: `${shortId(info.parent.componentId)} · ${info.slot}`,\n }\n }\n\n // Leaf root with no declared slots (degenerate graphs): append to `children`.\n return { target: { parentId: root.id, slot: 'children' }, destinationLabel: 'root' }\n}\n"]}
@@ -1,4 +1,5 @@
1
1
  import type { ComponentNode, GridContainerOptions, InsertTarget } from '@elytracms/core/project-graph';
2
+ import type { ComponentManifest } from '@elytracms/core/component-registry';
2
3
  /**
3
4
  * Drag & drop eligibility rules (EC-160 + EC-162, vision §3.1) — pure
4
5
  * graph-level functions with **no DOM or UI dependencies**. The canvas and the
@@ -25,6 +26,18 @@ export interface ManifestRules {
25
26
  }
26
27
  /** Resolve a component id to its manifest slot rules (undefined = unknown component). */
27
28
  export type ManifestLookup = (componentId: string) => ManifestRules | undefined;
29
+ /**
30
+ * The canonical `ManifestRules` view of a full manifest (EC-386): the declared
31
+ * `slots` merged with the blocks-prop-derived slots (`manifestSlots`, EC-190
32
+ * "slots are blocks props" — a `blocks`-type prop IS a named child region whose
33
+ * subtrees live in `node.slots[propName]`, carrying the prop's `allow` leash).
34
+ * Every surface that feeds a real `ComponentManifest` into a {@link ManifestLookup}
35
+ * or {@link primaryInsertSlot} must adapt through this — resolving against raw
36
+ * `manifest.slots` silently degrades a `project.Section.body`-style region to a
37
+ * sibling insert. One derivation, so tree insert, DnD, paste, and the canvas
38
+ * affordances can never disagree about which regions exist or what they allow.
39
+ */
40
+ export declare function manifestRules(manifest: ComponentManifest | undefined): ManifestRules | undefined;
28
41
  /** What is being dragged. */
29
42
  export type DragSubject =
30
43
  /** An existing node of the current page — a completed drop is a `move`. */
@@ -1,4 +1,19 @@
1
1
  import { DEFAULT_SLOT, GRID_COMPONENT_ID, OUTLET_SLOT, STACK_COMPONENT_ID, getNode, getParentInfo, gridContainerNode, isDescendantOf, isStaticProp, removeNode, updateNode, } from '@elytracms/core/project-graph';
2
+ import { manifestSlots } from '@elytracms/core/component-registry';
3
+ /**
4
+ * The canonical `ManifestRules` view of a full manifest (EC-386): the declared
5
+ * `slots` merged with the blocks-prop-derived slots (`manifestSlots`, EC-190
6
+ * "slots are blocks props" — a `blocks`-type prop IS a named child region whose
7
+ * subtrees live in `node.slots[propName]`, carrying the prop's `allow` leash).
8
+ * Every surface that feeds a real `ComponentManifest` into a {@link ManifestLookup}
9
+ * or {@link primaryInsertSlot} must adapt through this — resolving against raw
10
+ * `manifest.slots` silently degrades a `project.Section.body`-style region to a
11
+ * sibling insert. One derivation, so tree insert, DnD, paste, and the canvas
12
+ * affordances can never disagree about which regions exist or what they allow.
13
+ */
14
+ export function manifestRules(manifest) {
15
+ return manifest ? { slots: manifestSlots(manifest) } : undefined;
16
+ }
2
17
  function rejected(code, reason) {
3
18
  return { eligible: false, code, reason };
4
19
  }