@zitadel/components 0.1.0-alpha.14 → 0.1.0-alpha.16

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 (32) hide show
  1. package/README.md +1 -0
  2. package/dist/atoms/index.d.mts +1 -1
  3. package/dist/atoms/index.mjs +1 -1
  4. package/dist/{atoms-BSwVl736.mjs → atoms-C-zXJjaz.mjs} +106 -10
  5. package/dist/{atoms-BSwVl736.mjs.map → atoms-C-zXJjaz.mjs.map} +1 -1
  6. package/dist/default-BoC3JEsO.mjs +6 -0
  7. package/dist/default-BoC3JEsO.mjs.map +1 -0
  8. package/dist/{index-EC-BnGsx.d.mts → index-BniwXaIC.d.mts} +3 -1
  9. package/dist/{index-EC-BnGsx.d.mts.map → index-BniwXaIC.d.mts.map} +1 -1
  10. package/dist/{index-Bypr00UG.d.mts → index-BwQLFs9R.d.mts} +362 -17
  11. package/dist/index-BwQLFs9R.d.mts.map +1 -0
  12. package/dist/{index-BP3l3mje.d.mts → index-D1eMaWbY.d.mts} +52 -5
  13. package/dist/index-D1eMaWbY.d.mts.map +1 -0
  14. package/dist/index.d.mts +4 -4
  15. package/dist/index.mjs +4 -4
  16. package/dist/manifests.mjs +1 -1
  17. package/dist/orchestrator/index.d.mts +2 -2
  18. package/dist/orchestrator/index.mjs +2 -2
  19. package/dist/{orchestrator-qkFBKFgJ.mjs → orchestrator-BN2nT9FZ.mjs} +317 -25
  20. package/dist/orchestrator-BN2nT9FZ.mjs.map +1 -0
  21. package/dist/standalone.mjs +423 -33
  22. package/dist/tokens/index.d.mts +1 -1
  23. package/dist/tokens/index.mjs +1 -1
  24. package/dist/{tokens-C0j4iCrs.mjs → tokens-CbH2s1jV.mjs} +6 -4
  25. package/dist/tokens-CbH2s1jV.mjs.map +1 -0
  26. package/package.json +5 -4
  27. package/dist/default-W5MwYLBc.mjs +0 -6
  28. package/dist/default-W5MwYLBc.mjs.map +0 -1
  29. package/dist/index-BP3l3mje.d.mts.map +0 -1
  30. package/dist/index-Bypr00UG.d.mts.map +0 -1
  31. package/dist/orchestrator-qkFBKFgJ.mjs.map +0 -1
  32. package/dist/tokens-C0j4iCrs.mjs.map +0 -1
@@ -823,7 +823,8 @@ const tokens = {
823
823
  "container": {
824
824
  "authCard": "24rem",
825
825
  "page": "80rem"
826
- }
826
+ },
827
+ "layout": {}
827
828
  };
828
829
  const cssVars = {
829
830
  "color": {
@@ -942,9 +943,10 @@ const cssVars = {
942
943
  "container": {
943
944
  "authCard": "var(--zl-container-auth-card)",
944
945
  "page": "var(--zl-container-page)"
945
- }
946
+ },
947
+ "layout": {}
946
948
  };
947
- const tokensCss = "/* AUTOGENERATED by packages/design-tokens/scripts/build.ts — do not edit by hand.\n Source: src/generated/figma.tokens.json + src/overrides.ts.\n Re-run: moon run design-tokens:generate */\n\n:root,\n[data-theme=\"dark\"] {\n --zl-color-surface-default-black: #0f0f11;\n --zl-color-surface-default-white: #f4f4f6;\n --zl-color-surface-default-primary-gray: #252528;\n --zl-color-surface-default-secondary-gray: #37373a;\n --zl-color-text-primary-white: #f4f4f6;\n --zl-color-text-secondary-gray: #bfbfcf;\n --zl-color-text-button-default: #0f0f11;\n --zl-color-text-button-invert: #f4f4f6;\n --zl-color-text-disabled: #686883;\n --zl-color-text-error: #ea4f70;\n --zl-color-text-success: #33a779;\n --zl-color-text-subtitle-pink: #f48aa0;\n --zl-color-text-subtitle-purple: #bba5e4;\n --zl-color-text-subtitle-gray: #bfbfcf;\n --zl-color-text-subtitle-orange: #f25543;\n --zl-color-icon-default-black: #0f0f11;\n --zl-color-icon-default-white: #f4f4f6;\n --zl-color-icon-default-gray: #bfbfcf;\n --zl-color-icon-default-pink: #f48aa0;\n --zl-color-icon-default-purple: #bba5e4;\n --zl-color-icon-default-orange: #f25543;\n --zl-color-icon-disabled: #686883;\n --zl-color-icon-error: #ea4f70;\n --zl-color-icon-success: #33a779;\n --zl-color-border-default-black: #252528;\n --zl-color-border-default-white: #f4f4f6;\n --zl-color-border-default-gray-100: #37373a;\n --zl-color-border-default-gray-200: #484a57;\n --zl-color-border-default-gray-300: #686883;\n --zl-color-border-default-gray-400: #82829c;\n --zl-color-border-error: #ea4f70;\n --zl-color-border-success: #33a779;\n --zl-color-gray-50: #0f0f11;\n --zl-color-gray-75: #252528;\n --zl-color-gray-100: #37373a;\n --zl-color-gray-200: #484a57;\n --zl-color-gray-300: #686883;\n --zl-color-gray-400: #82829c;\n --zl-color-gray-500: #9e9eb2;\n --zl-color-gray-600: #cfcfde;\n --zl-color-gray-700: #bfbfcf;\n --zl-color-gray-800: #e1e1e7;\n --zl-color-gray-900: #f4f4f6;\n --zl-spacing-01: 0.25rem;\n --zl-spacing-02: 0.5rem;\n --zl-spacing-03: 1rem;\n --zl-spacing-04: 1.5rem;\n --zl-spacing-05: 2rem;\n --zl-spacing-06: 1.5rem;\n --zl-spacing-07: 2.5rem;\n --zl-spacing-08: 3rem;\n --zl-spacing-09: 3.5rem;\n --zl-spacing-10: 4rem;\n --zl-spacing-11: 5rem;\n --zl-spacing-12: 5.5rem;\n --zl-spacing-13: 6rem;\n --zl-spacing-14: 6.5rem;\n --zl-spacing-15: 7rem;\n --zl-spacing-16: 7.5rem;\n --zl-radius-xs: 0.25rem;\n --zl-radius-s: 0.5rem;\n --zl-radius-m: 0.75rem;\n --zl-radius-l: 1rem;\n --zl-radius-xl: 1.5rem;\n --zl-font-family-sans: \"Arimo\", system-ui, -apple-system, \"Segoe UI\", Helvetica, Arial, sans-serif;\n --zl-font-family-heading: \"Arimo\", system-ui, -apple-system, \"Segoe UI\", Helvetica, Arial, sans-serif;\n --zl-font-family-mono: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n --zl-duration-instant: 0ms;\n --zl-duration-fast: 120ms;\n --zl-duration-base: 200ms;\n --zl-duration-slow: 320ms;\n --zl-easing-standard: cubic-bezier(0.2, 0, 0, 1);\n --zl-easing-decelerate: cubic-bezier(0, 0, 0, 1);\n --zl-easing-accelerate: cubic-bezier(0.3, 0, 1, 1);\n --zl-focus-width: 2px;\n --zl-focus-offset: 2px;\n --zl-focus-color: #f4f4f6;\n --zl-breakpoint-xs: 26.5625rem;\n --zl-breakpoint-sm: 40rem;\n --zl-breakpoint-md: 48rem;\n --zl-breakpoint-lg: 64rem;\n --zl-breakpoint-xl: 80rem;\n --zl-breakpoint-2xl: 96rem;\n --zl-breakpoint-3xl: 120rem;\n --zl-breakpoint-4xl: 160rem;\n --zl-container-auth-card: 24rem;\n --zl-container-page: 80rem;\n}\n\n[data-theme=\"light\"] {\n /* Reserved. Light mode lands when the Figma design system publishes\n a light variable mode; the next sync will populate this block. */\n}\n";
949
+ const tokensCss = "/* AUTOGENERATED by packages/design-tokens/scripts/build.ts — do not edit by hand.\n Source: src/generated/figma.tokens.json + src/overrides.ts.\n Re-run: moon run design-tokens:generate */\n\n:root,\n[data-theme=\"dark\"] {\n --zl-color-surface-default-black: #0f0f11;\n --zl-color-surface-default-white: #f4f4f6;\n --zl-color-surface-default-primary-gray: #252528;\n --zl-color-surface-default-secondary-gray: #37373a;\n --zl-color-text-primary-white: #f4f4f6;\n --zl-color-text-secondary-gray: #bfbfcf;\n --zl-color-text-button-default: #0f0f11;\n --zl-color-text-button-invert: #f4f4f6;\n --zl-color-text-disabled: #686883;\n --zl-color-text-error: #ea4f70;\n --zl-color-text-success: #33a779;\n --zl-color-text-subtitle-pink: #f48aa0;\n --zl-color-text-subtitle-purple: #bba5e4;\n --zl-color-text-subtitle-gray: #bfbfcf;\n --zl-color-text-subtitle-orange: #f25543;\n --zl-color-icon-default-black: #0f0f11;\n --zl-color-icon-default-white: #f4f4f6;\n --zl-color-icon-default-gray: #bfbfcf;\n --zl-color-icon-default-pink: #f48aa0;\n --zl-color-icon-default-purple: #bba5e4;\n --zl-color-icon-default-orange: #f25543;\n --zl-color-icon-disabled: #686883;\n --zl-color-icon-error: #ea4f70;\n --zl-color-icon-success: #33a779;\n --zl-color-border-default-black: #252528;\n --zl-color-border-default-white: #f4f4f6;\n --zl-color-border-default-gray-100: #37373a;\n --zl-color-border-default-gray-200: #484a57;\n --zl-color-border-default-gray-300: #686883;\n --zl-color-border-default-gray-400: #82829c;\n --zl-color-border-error: #ea4f70;\n --zl-color-border-success: #33a779;\n --zl-color-gray-50: #0f0f11;\n --zl-color-gray-75: #252528;\n --zl-color-gray-100: #37373a;\n --zl-color-gray-200: #484a57;\n --zl-color-gray-300: #686883;\n --zl-color-gray-400: #82829c;\n --zl-color-gray-500: #9e9eb2;\n --zl-color-gray-600: #cfcfde;\n --zl-color-gray-700: #bfbfcf;\n --zl-color-gray-800: #e1e1e7;\n --zl-color-gray-900: #f4f4f6;\n --zl-spacing-01: 0.25rem;\n --zl-spacing-02: 0.5rem;\n --zl-spacing-03: 1rem;\n --zl-spacing-04: 1.5rem;\n --zl-spacing-05: 2rem;\n --zl-spacing-06: 1.5rem;\n --zl-spacing-07: 2.5rem;\n --zl-spacing-08: 3rem;\n --zl-spacing-09: 3.5rem;\n --zl-spacing-10: 4rem;\n --zl-spacing-11: 5rem;\n --zl-spacing-12: 5.5rem;\n --zl-spacing-13: 6rem;\n --zl-spacing-14: 6.5rem;\n --zl-spacing-15: 7rem;\n --zl-spacing-16: 7.5rem;\n --zl-radius-xs: 0.25rem;\n --zl-radius-s: 0.5rem;\n --zl-radius-m: 0.75rem;\n --zl-radius-l: 1rem;\n --zl-radius-xl: 1.5rem;\n --zl-font-family-sans: \"Arimo\", system-ui, -apple-system, \"Segoe UI\", Helvetica, Arial, sans-serif;\n --zl-font-family-heading: \"Arimo\", system-ui, -apple-system, \"Segoe UI\", Helvetica, Arial, sans-serif;\n --zl-font-family-mono: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n --zl-duration-instant: 0ms;\n --zl-duration-fast: 120ms;\n --zl-duration-base: 200ms;\n --zl-duration-slow: 320ms;\n --zl-easing-standard: cubic-bezier(0.2, 0, 0, 1);\n --zl-easing-decelerate: cubic-bezier(0, 0, 0, 1);\n --zl-easing-accelerate: cubic-bezier(0.3, 0, 1, 1);\n --zl-focus-width: 2px;\n --zl-focus-offset: 2px;\n --zl-focus-color: #f4f4f6;\n --zl-breakpoint-xs: 26.5625rem;\n --zl-breakpoint-sm: 40rem;\n --zl-breakpoint-md: 48rem;\n --zl-breakpoint-lg: 64rem;\n --zl-breakpoint-xl: 80rem;\n --zl-breakpoint-2xl: 96rem;\n --zl-breakpoint-3xl: 120rem;\n --zl-breakpoint-4xl: 160rem;\n --zl-container-auth-card: 24rem;\n --zl-container-page: 80rem;\n}\n\n[data-theme=\"light\"] {\n}\n";
948
950
  //#endregion
949
951
  //#region src/styles/tokens.ts
950
952
  function wrap(node) {
@@ -2583,6 +2585,13 @@ function base64UrlToBuffer(base64url) {
2583
2585
  }
2584
2586
  //#endregion
2585
2587
  //#region src/atoms/zl-passkey.ts
2588
+ /**
2589
+ * Slack subtracted from `options.timeout` when classifying a
2590
+ * `NotAllowedError` as a timeout: browsers clamp and fire the deadline
2591
+ * slightly early, so an exact comparison would label real timeouts as
2592
+ * user cancellations.
2593
+ */
2594
+ const TIMEOUT_GRACE_MS = 1e3;
2586
2595
  let ZlPasskey = class ZlPasskey extends i$2 {
2587
2596
  #_ceremony_accessor_storage = "authenticate";
2588
2597
  get ceremony() {
@@ -2619,7 +2628,37 @@ let ZlPasskey = class ZlPasskey extends i$2 {
2619
2628
  set manual(value) {
2620
2629
  this.#_manual_accessor_storage = value;
2621
2630
  }
2631
+ #_pendingLabel_accessor_storage = "Waiting for your passkey…";
2632
+ get pendingLabel() {
2633
+ return this.#_pendingLabel_accessor_storage;
2634
+ }
2635
+ set pendingLabel(value) {
2636
+ this.#_pendingLabel_accessor_storage = value;
2637
+ }
2638
+ #_cancelLabel_accessor_storage = "Cancel";
2639
+ get cancelLabel() {
2640
+ return this.#_cancelLabel_accessor_storage;
2641
+ }
2642
+ set cancelLabel(value) {
2643
+ this.#_cancelLabel_accessor_storage = value;
2644
+ }
2645
+ #_silent_accessor_storage = false;
2646
+ get silent() {
2647
+ return this.#_silent_accessor_storage;
2648
+ }
2649
+ set silent(value) {
2650
+ this.#_silent_accessor_storage = value;
2651
+ }
2652
+ #_pending_accessor_storage = false;
2653
+ get pending() {
2654
+ return this.#_pending_accessor_storage;
2655
+ }
2656
+ set pending(value) {
2657
+ this.#_pending_accessor_storage = value;
2658
+ }
2622
2659
  abortController = null;
2660
+ /** `Date.now()` at ceremony start, for timeout classification on reject. */
2661
+ startedAt = 0;
2623
2662
  connectedCallback() {
2624
2663
  super.connectedCallback();
2625
2664
  if (!this.manual && this.options) this.startCeremony();
@@ -2630,11 +2669,14 @@ let ZlPasskey = class ZlPasskey extends i$2 {
2630
2669
  }
2631
2670
  /**
2632
2671
  * Abort any in-flight WebAuthn ceremony. Called automatically when the
2633
- * component disconnects (step change, page navigation).
2672
+ * component disconnects (step change, page navigation) and by the
2673
+ * pending UI's cancel button. Clears `pending` immediately — the
2674
+ * ceremony's own `finally` no longer owns it (its controller is gone).
2634
2675
  */
2635
2676
  abort() {
2636
2677
  this.abortController?.abort();
2637
2678
  this.abortController = null;
2679
+ this.pending = false;
2638
2680
  }
2639
2681
  /**
2640
2682
  * Trigger the WebAuthn ceremony. Resolves when the ceremony completes
@@ -2650,7 +2692,11 @@ let ZlPasskey = class ZlPasskey extends i$2 {
2650
2692
  return;
2651
2693
  }
2652
2694
  this.abort();
2653
- this.abortController = new AbortController();
2695
+ const controller = new AbortController();
2696
+ this.abortController = controller;
2697
+ this.pending = true;
2698
+ this.startedAt = Date.now();
2699
+ this.emitStarted();
2654
2700
  try {
2655
2701
  const credential = this.ceremony === "register" ? await this.createCredential() : await this.getCredential();
2656
2702
  if (!credential) {
@@ -2661,9 +2707,33 @@ let ZlPasskey = class ZlPasskey extends i$2 {
2661
2707
  this.emitResult(proof);
2662
2708
  } catch (error) {
2663
2709
  const aborted = error instanceof DOMException && (error.name === "AbortError" || error.name === "NotAllowedError");
2664
- this.emitError(error instanceof Error ? error.message : "WebAuthn ceremony failed.", aborted);
2710
+ const timeoutMs = typeof this.options?.timeout === "number" ? this.options.timeout : null;
2711
+ const timedOut = error instanceof DOMException && error.name === "NotAllowedError" && timeoutMs !== null && Date.now() - this.startedAt >= timeoutMs - TIMEOUT_GRACE_MS;
2712
+ this.emitError(error instanceof Error ? error.message : "WebAuthn ceremony failed.", aborted, timedOut);
2713
+ } finally {
2714
+ if (this.abortController === controller) this.pending = false;
2665
2715
  }
2666
2716
  }
2717
+ handleCancelSubmit = (event) => {
2718
+ event.stopPropagation();
2719
+ this.abort();
2720
+ };
2721
+ render() {
2722
+ if (!this.pending || this.silent) return A;
2723
+ return b`
2724
+ <div class="zl-passkey-pending" data-testid="zitadel-passkey-pending">
2725
+ <zl-alert severity="info">${this.pendingLabel}</zl-alert>
2726
+ <zl-button
2727
+ hierarchy="secondary"
2728
+ type="button"
2729
+ block
2730
+ data-testid="zitadel-passkey-cancel"
2731
+ label=${this.cancelLabel}
2732
+ @zl-submit=${this.handleCancelSubmit}
2733
+ ></zl-button>
2734
+ </div>
2735
+ `;
2736
+ }
2667
2737
  /**
2668
2738
  * `navigator.credentials.get()` — authentication ceremony.
2669
2739
  */
@@ -2765,11 +2835,12 @@ let ZlPasskey = class ZlPasskey extends i$2 {
2765
2835
  detail
2766
2836
  }));
2767
2837
  }
2768
- emitError(message, aborted) {
2838
+ emitError(message, aborted, timedOut = false) {
2769
2839
  const detail = {
2770
2840
  challenge_id: this.challengeId,
2771
2841
  error: message,
2772
- aborted
2842
+ aborted,
2843
+ timed_out: timedOut
2773
2844
  };
2774
2845
  this.dispatchEvent(new CustomEvent("zl-passkey-error", {
2775
2846
  bubbles: true,
@@ -2777,7 +2848,23 @@ let ZlPasskey = class ZlPasskey extends i$2 {
2777
2848
  detail
2778
2849
  }));
2779
2850
  }
2780
- /** Invisible component — no shadow DOM rendering needed. */
2851
+ emitStarted() {
2852
+ const detail = {
2853
+ challenge_id: this.challengeId,
2854
+ method: this.method
2855
+ };
2856
+ this.dispatchEvent(new CustomEvent("zl-passkey-started", {
2857
+ bubbles: true,
2858
+ composed: true,
2859
+ detail
2860
+ }));
2861
+ }
2862
+ /**
2863
+ * Light DOM on purpose: the pending UI must land inside the
2864
+ * orchestrator's shadow root (where the step's adopted styles apply)
2865
+ * without altering the `unsafeHTML` step markup — runtime children don't
2866
+ * participate in its string comparison, so the ceremony never restarts.
2867
+ */
2781
2868
  createRenderRoot() {
2782
2869
  return this;
2783
2870
  }
@@ -2802,6 +2889,10 @@ __decorate([n$1({
2802
2889
  })], ZlPasskey.prototype, "options", null);
2803
2890
  __decorate([n$1({ type: String })], ZlPasskey.prototype, "method", null);
2804
2891
  __decorate([n$1({ type: Boolean })], ZlPasskey.prototype, "manual", null);
2892
+ __decorate([n$1({ attribute: "pending-label" })], ZlPasskey.prototype, "pendingLabel", null);
2893
+ __decorate([n$1({ attribute: "cancel-label" })], ZlPasskey.prototype, "cancelLabel", null);
2894
+ __decorate([n$1({ type: Boolean })], ZlPasskey.prototype, "silent", null);
2895
+ __decorate([r$1()], ZlPasskey.prototype, "pending", null);
2805
2896
  ZlPasskey = __decorate([t$4("zl-passkey")], ZlPasskey);
2806
2897
  const zlPasskeyManifest = {
2807
2898
  tag: "zl-passkey",
@@ -2811,11 +2902,18 @@ const zlPasskeyManifest = {
2811
2902
  "method",
2812
2903
  "challenge-id",
2813
2904
  "options",
2814
- "manual"
2905
+ "manual",
2906
+ "pending-label",
2907
+ "cancel-label",
2908
+ "silent"
2815
2909
  ],
2816
2910
  parts: [],
2817
2911
  slots: [],
2818
- events: ["zl-passkey-result", "zl-passkey-error"]
2912
+ events: [
2913
+ "zl-passkey-result",
2914
+ "zl-passkey-error",
2915
+ "zl-passkey-started"
2916
+ ]
2819
2917
  };
2820
2918
  //#endregion
2821
2919
  //#region ../shared-component-styles/src/lit/pill-host.css?inline
@@ -3365,6 +3463,37 @@ function safeJsonParse(text) {
3365
3463
  return { raw: text };
3366
3464
  }
3367
3465
  }
3466
+ /**
3467
+ * Extracts the server's `{code, message, details}` envelope from an
3468
+ * {@link ApiError} into a display string. Falls back to the fetch layer's
3469
+ * `"METHOD url returned N"` when the body isn't shaped like an envelope, so
3470
+ * transport-level failures (HTML from a proxy, empty 5xx) still say
3471
+ * something.
3472
+ */
3473
+ function apiErrorMessage(error) {
3474
+ const body = error.body;
3475
+ if (!isRecord(body)) return error.message;
3476
+ const serverMessage = typeof body.message === "string" ? body.message : void 0;
3477
+ const detail = pickDetailString(body.details);
3478
+ if (!serverMessage) return error.message;
3479
+ return detail ? `${serverMessage}: ${detail}` : serverMessage;
3480
+ }
3481
+ /**
3482
+ * Reads the innermost human-readable string out of the spec's error
3483
+ * `details` field. Handles both `details: "..."` and the nested
3484
+ * `details: { details: "..." }` shape the platform emits for validation
3485
+ * failures.
3486
+ */
3487
+ function pickDetailString(details) {
3488
+ if (typeof details === "string") return details;
3489
+ if (isRecord(details)) {
3490
+ if (typeof details.details === "string") return details.details;
3491
+ if (typeof details.message === "string") return details.message;
3492
+ }
3493
+ }
3494
+ function isRecord(value) {
3495
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3496
+ }
3368
3497
  //#endregion
3369
3498
  //#region src/orchestrator/api-client.ts
3370
3499
  const apiRequestInit = { credentials: "include" };
@@ -3401,7 +3530,7 @@ async function exchangeSession(api, body, params) {
3401
3530
  * orchestrator's signed-in surfaces to render the user's identity.
3402
3531
  */
3403
3532
  async function getSession$1(api) {
3404
- return await api.getMySession(apiRequestInit);
3533
+ return api.getMySession(apiRequestInit);
3405
3534
  }
3406
3535
  /**
3407
3536
  * Revokes the current session (`DELETE /sessions/me`) with credentials. The
@@ -3505,7 +3634,9 @@ var zitadelNextGen_exports = /* @__PURE__ */ __exportAll({
3505
3634
  getMySession: () => getMySession,
3506
3635
  getMyUser: () => getMyUser,
3507
3636
  getOpenIDConfiguration: () => getOpenIDConfiguration,
3637
+ getPatchProjectUrl: () => getPatchProjectUrl,
3508
3638
  getProject: () => getProject,
3639
+ getQueryProjectsUrl: () => getQueryProjectsUrl,
3509
3640
  getReady: () => getReady,
3510
3641
  getRevokeMySessionUrl: () => getRevokeMySessionUrl,
3511
3642
  getRevokeSessionUrl: () => getRevokeSessionUrl,
@@ -3527,6 +3658,8 @@ var zitadelNextGen_exports = /* @__PURE__ */ __exportAll({
3527
3658
  listSchemas: () => listSchemas,
3528
3659
  listSessions: () => listSessions,
3529
3660
  listUsers: () => listUsers,
3661
+ patchProject: () => patchProject,
3662
+ queryProjects: () => queryProjects,
3530
3663
  revokeMySession: () => revokeMySession,
3531
3664
  revokeSession: () => revokeSession,
3532
3665
  revokeToken: () => revokeToken,
@@ -4065,6 +4198,23 @@ const createProject = async (createProjectBody, options) => {
4065
4198
  body: JSON.stringify(createProjectBody)
4066
4199
  });
4067
4200
  };
4201
+ const getQueryProjectsUrl = () => {
4202
+ return `${getProxyPath()}/projects/query`;
4203
+ };
4204
+ /**
4205
+ * @summary Query projects
4206
+ */
4207
+ const queryProjects = async (queryProjectsBody, options) => {
4208
+ return customFetch(getQueryProjectsUrl(), {
4209
+ ...options,
4210
+ method: "POST",
4211
+ headers: {
4212
+ "Content-Type": "application/json",
4213
+ ...options?.headers
4214
+ },
4215
+ body: JSON.stringify(queryProjectsBody)
4216
+ });
4217
+ };
4068
4218
  const getGetProjectUrl = (projectId) => {
4069
4219
  return `${getProxyPath()}/projects/${projectId}`;
4070
4220
  };
@@ -4079,6 +4229,25 @@ const getProject = async (projectId, options) => {
4079
4229
  method: "GET"
4080
4230
  });
4081
4231
  };
4232
+ const getPatchProjectUrl = (projectId) => {
4233
+ return `${getProxyPath()}/projects/${projectId}`;
4234
+ };
4235
+ /**
4236
+ * Updates the state of a project.
4237
+
4238
+ * @summary Update project
4239
+ */
4240
+ const patchProject = async (projectId, patchProjectBody, options) => {
4241
+ return customFetch(getPatchProjectUrl(projectId), {
4242
+ ...options,
4243
+ method: "PATCH",
4244
+ headers: {
4245
+ "Content-Type": "application/json",
4246
+ ...options?.headers
4247
+ },
4248
+ body: JSON.stringify(patchProjectBody)
4249
+ });
4250
+ };
4082
4251
  const getCreateSessionUrl = () => {
4083
4252
  return `${getProxyPath()}/sessions`;
4084
4253
  };
@@ -9506,7 +9675,7 @@ function lookup(locale, key) {
9506
9675
  }
9507
9676
  //#endregion
9508
9677
  //#region src/orchestrator/templates/default.liquid
9509
- var default_default = "<zl-page-shell data-zl-template-root>\n {% if branding.logo_url %}\n <img slot=\"header\" class=\"zl-card-logo\" src=\"{{ branding.logo_url }}\" alt=\"\" />\n {% endif %}\n <zl-card>\n <h1 slot=\"header\" class=\"zl-card-title\">{{ step.texts.title_key | t: identity.display_name }}</h1>\n {% if step.texts.description_key %}\n {% assign card_subtitle = step.texts.description_key | t: identity.display_name %}\n {% if card_subtitle != \"\" %}\n <p slot=\"header\" class=\"zl-card-subtitle\">{{ card_subtitle }}</p>\n {% endif %}\n {% endif %}\n\n {% if errors and errors.size > 0 %}\n {% for err in errors %}\n {% if err | formLevelError %}\n {% assign alert_heading = err.text_key | alertHeading %}\n {% assign alert_body = err.text_key | alertBody %}\n <zl-alert severity=\"error\"{% if alert_heading != \"\" %} heading=\"{{ alert_heading | t }}\"{% endif %}>\n {% if alert_body != \"\" %}{{ alert_body | t }}{% elsif err.text_key %}{{ err.text_key | t }}{% else %}{{ err.message }}{% endif %}\n </zl-alert>\n {% endif %}\n {% endfor %}\n {% endif %}\n\n {% for f in fields %}\n {% assign field_placeholder = f.text_key | fieldPlaceholder %}\n {% assign field_help = f.text_key | fieldHelp %}\n {% assign field_err = f.name | fieldError: errors %}\n {% case f.type %}\n {% when 'checkbox' %}\n <zl-checkbox\n name=\"{{ f.name }}\"\n data-testid=\"zitadel-field-{{ f.name }}\"\n label=\"{{ f.text_key | t }}\"\n value=\"true\"\n {% if f.value %}checked{% endif %}\n {% if f.required %}required{% endif %}\n ></zl-checkbox>\n {% when 'select' %}\n <zl-select\n name=\"{{ f.name }}\"\n data-testid=\"zitadel-field-{{ f.name }}\"\n label=\"{{ f.text_key | t }}\"\n value=\"{{ f.value }}\"\n options='{{ f.validation.enum | selectOptions | json }}'\n {% if f.required %}required{% endif %}\n {% if field_placeholder != \"\" %}placeholder=\"{{ field_placeholder }}\"{% endif %}\n ></zl-select>\n {% else %}\n {% assign autocomplete_val = '' %}\n {% if f.name == 'email' %}\n {% assign autocomplete_val = 'email' %}\n {% elsif f.type == 'password' and step.name contains 'register' %}\n {% assign autocomplete_val = 'new-password' %}\n {% elsif f.type == 'password' %}\n {% assign autocomplete_val = 'current-password' %}\n {% endif %}\n <zl-field\n name=\"{{ f.name }}\"\n data-testid=\"zitadel-field-{{ f.name }}\"\n label=\"{{ f.text_key | t }}\"\n type=\"{{ f.type }}\"\n value=\"{{ f.value }}\"\n {% if autocomplete_val != '' %}autocomplete=\"{{ autocomplete_val }}\"{% endif %}\n {% if f.required %}required{% endif %}\n {% if field_placeholder != \"\" %}placeholder=\"{{ field_placeholder }}\"{% endif %}\n {% if field_err != \"\" %}invalid error=\"{{ field_err }}\"{% endif %}\n >\n {% if field_help != \"\" %}\n <span slot=\"help\">{{ field_help }}</span>\n {% endif %}\n </zl-field>\n {% endcase %}\n {% endfor %}\n\n {% assign recover = actions | where: \"name\", \"recover\" | first %}\n {% if recover %}\n <p class=\"zl-card-forgot\">\n <a href=\"#\" class=\"zl-card-forgot__link\" data-action=\"recover\">{{ recover.text_key | t }}</a>\n </p>\n {% endif %}\n\n {% for a in actions %}\n {% if a.primary %}\n <zl-button\n hierarchy=\"primary\"\n size=\"medium\"\n type=\"submit\"\n block\n action=\"{{ a.name }}\"\n data-testid=\"zitadel-action-{{ a.name }}\"\n label=\"{{ a.text_key | t }}\"\n {% if loading %}loading{% endif %}\n ></zl-button>\n {% endif %}\n {% endfor %}\n\n {% for a in actions %}\n {% unless a.primary or a.name == 'passkey' or a.name == 'register' or a.name == 'sign_in' or a.name == 'recover' %}\n <zl-button\n hierarchy=\"secondary\"\n size=\"medium\"\n type=\"button\"\n block\n action=\"{{ a.name }}\"\n data-testid=\"zitadel-action-{{ a.name }}\"\n label=\"{{ a.text_key | t }}\"\n ></zl-button>\n {% endunless %}\n {% endfor %}\n\n {% assign passkey = actions | where: \"name\", \"passkey\" | first %}\n {% if passkey %}\n <zl-button\n hierarchy=\"secondary\"\n size=\"medium\"\n block\n action=\"passkey\"\n data-testid=\"zitadel-action-passkey\"\n label=\"{{ passkey.text_key | t }}\"\n ></zl-button>\n {% endif %}\n\n\n {% assign register = actions | where: \"name\", \"register\" | first %}\n {% if register %}\n <p class=\"zl-card-nav\">\n {{ 'identifier.action.register.lead' | t }}<a href=\"#\" class=\"zl-card-nav__link\" data-action=\"register\" data-testid=\"zitadel-action-register-link\">{{ 'identifier.action.register.link' | t }}</a>\n </p>\n {% endif %}\n\n {% assign sign_in = actions | where: \"name\", \"sign_in\" | first %}\n {% if sign_in %}\n <p class=\"zl-card-nav\">\n {{ 'register.action.sign_in.lead' | t }}<a href=\"#\" class=\"zl-card-nav__link\" data-action=\"sign_in\" data-testid=\"zitadel-action-sign_in-link\">{{ 'register.action.sign_in.link' | t }}</a>\n </p>\n {% endif %}\n\n {% if challenge %}\n {% if challenge.method == \"passkey\" or challenge.method == \"passkey_register\" %}\n {% assign ceremony = \"authenticate\" %}\n {% assign submit_method = challenge.method %}\n {% if challenge.method == \"passkey_register\" or challenge.options.user %}\n {% assign ceremony = \"register\" %}\n {% assign submit_method = \"passkey_register\" %}\n {% endif %}\n <zl-passkey\n ceremony=\"{{ ceremony }}\"\n method=\"{{ submit_method }}\"\n challenge-id=\"{{ challenge.challenge_id }}\"\n options='{{ challenge.options | json }}'\n ></zl-passkey>\n {% endif %}\n {% endif %}\n\n {% mandatory_gates %}\n </zl-card>\n</zl-page-shell>\n";
9678
+ var default_default = "<zl-page-shell data-zl-template-root>\n {% if branding.logo_url %}\n <img slot=\"header\" class=\"zl-card-logo\" src=\"{{ branding.logo_url }}\" alt=\"\" />\n {% endif %}\n <zl-card>\n <h1 slot=\"header\" class=\"zl-card-title\">{{ step.texts.title_key | t: identity.display_name }}</h1>\n {% if step.texts.description_key %}\n {% assign card_subtitle = step.texts.description_key | t: identity.display_name %}\n {% if card_subtitle != \"\" %}\n <p slot=\"header\" class=\"zl-card-subtitle\">{{ card_subtitle }}</p>\n {% endif %}\n {% endif %}\n\n {% if errors and errors.size > 0 %}\n {% for err in errors %}\n {% if err | formLevelError %}\n {% assign alert_heading = err.text_key | alertHeading %}\n {% assign alert_body = err.text_key | alertBody %}\n <zl-alert severity=\"error\" dismissible data-zl-step-error{% if alert_heading != \"\" %} heading=\"{{ alert_heading | t }}\"{% endif %}>\n {% if alert_body != \"\" %}{{ alert_body | t }}{% elsif err.text_key %}{{ err.text_key | t }}{% else %}{{ err.message }}{% endif %}\n </zl-alert>\n {% endif %}\n {% endfor %}\n {% endif %}\n\n {% for f in fields %}\n {% assign field_placeholder = f.text_key | fieldPlaceholder %}\n {% assign field_help = f.text_key | fieldHelp %}\n {% assign field_err = f.name | fieldError: errors %}\n {% case f.type %}\n {% when 'checkbox' %}\n <zl-checkbox\n name=\"{{ f.name }}\"\n data-testid=\"zitadel-field-{{ f.name }}\"\n label=\"{{ f.text_key | t }}\"\n value=\"true\"\n {% if f.value %}checked{% endif %}\n {% if f.required %}required{% endif %}\n ></zl-checkbox>\n {% when 'select' %}\n <zl-select\n name=\"{{ f.name }}\"\n data-testid=\"zitadel-field-{{ f.name }}\"\n label=\"{{ f.text_key | t }}\"\n value=\"{{ f.value }}\"\n options='{{ f.validation.enum | selectOptions | json }}'\n {% if f.required %}required{% endif %}\n {% if field_placeholder != \"\" %}placeholder=\"{{ field_placeholder }}\"{% endif %}\n ></zl-select>\n {% else %}\n {% assign autocomplete_val = '' %}\n {% if f.name == 'email' %}\n {% assign autocomplete_val = 'email' %}\n {% elsif f.type == 'password' and step.name contains 'register' %}\n {% assign autocomplete_val = 'new-password' %}\n {% elsif f.type == 'password' %}\n {% assign autocomplete_val = 'current-password' %}\n {% endif %}\n <zl-field\n name=\"{{ f.name }}\"\n data-testid=\"zitadel-field-{{ f.name }}\"\n label=\"{{ f.text_key | t }}\"\n type=\"{{ f.type }}\"\n value=\"{{ f.value }}\"\n {% if autocomplete_val != '' %}autocomplete=\"{{ autocomplete_val }}\"{% endif %}\n {% if f.required %}required{% endif %}\n {% if field_placeholder != \"\" %}placeholder=\"{{ field_placeholder }}\"{% endif %}\n {% if field_err != \"\" %}invalid error=\"{{ field_err }}\"{% endif %}\n >\n {% if field_help != \"\" %}\n <span slot=\"help\">{{ field_help }}</span>\n {% endif %}\n </zl-field>\n {% endcase %}\n {% endfor %}\n\n {% assign recover = actions | where: \"name\", \"recover\" | first %}\n {% if recover %}\n <p class=\"zl-card-forgot\">\n <a href=\"#\" class=\"zl-card-forgot__link\" data-action=\"recover\">{{ recover.text_key | t }}</a>\n </p>\n {% endif %}\n\n {% for a in actions %}\n {% if a.primary %}\n <zl-button\n hierarchy=\"primary\"\n size=\"medium\"\n type=\"submit\"\n block\n action=\"{{ a.name }}\"\n data-testid=\"zitadel-action-{{ a.name }}\"\n label=\"{{ a.text_key | t }}\"\n {% if loading %}loading{% endif %}\n ></zl-button>\n {% endif %}\n {% endfor %}\n\n {% for a in actions %}\n {% unless a.primary or a.name == 'passkey' or a.name == 'register' or a.name == 'sign_in' or a.name == 'recover' %}\n <zl-button\n hierarchy=\"secondary\"\n size=\"medium\"\n type=\"button\"\n block\n action=\"{{ a.name }}\"\n data-testid=\"zitadel-action-{{ a.name }}\"\n label=\"{{ a.text_key | t }}\"\n ></zl-button>\n {% endunless %}\n {% endfor %}\n\n {% assign passkey = actions | where: \"name\", \"passkey\" | first %}\n {% if passkey %}\n {% unless passkey.primary %}\n <zl-button\n hierarchy=\"secondary\"\n size=\"medium\"\n block\n action=\"passkey\"\n data-testid=\"zitadel-action-passkey\"\n label=\"{{ passkey.text_key | t }}\"\n ></zl-button>\n {% endunless %}\n {% endif %}\n\n\n {% assign register = actions | where: \"name\", \"register\" | first %}\n {% if register %}\n <p class=\"zl-card-nav\">\n {{ 'identifier.action.register.lead' | t }}<a href=\"#\" class=\"zl-card-nav__link\" data-action=\"register\" data-testid=\"zitadel-action-register-link\">{{ 'identifier.action.register.link' | t }}</a>\n </p>\n {% endif %}\n\n {% assign sign_in = actions | where: \"name\", \"sign_in\" | first %}\n {% if sign_in %}\n <p class=\"zl-card-nav\">\n {{ 'register.action.sign_in.lead' | t }}<a href=\"#\" class=\"zl-card-nav__link\" data-action=\"sign_in\" data-testid=\"zitadel-action-sign_in-link\">{{ 'register.action.sign_in.link' | t }}</a>\n </p>\n {% endif %}\n\n {% if challenge %}\n {% if challenge.method == \"passkey\" or challenge.method == \"passkey_register\" %}\n {% assign ceremony = \"authenticate\" %}\n {% assign submit_method = challenge.method %}\n {% if challenge.method == \"passkey_register\" or challenge.options.user %}\n {% assign ceremony = \"register\" %}\n {% assign submit_method = \"passkey_register\" %}\n {% endif %}\n <zl-passkey\n ceremony=\"{{ ceremony }}\"\n method=\"{{ submit_method }}\"\n challenge-id=\"{{ challenge.challenge_id }}\"\n options='{{ challenge.options | json }}'\n pending-label=\"{{ 'passkey.pending.status' | t }}\"\n cancel-label=\"{{ 'action.cancel' | t }}\"\n ></zl-passkey>\n {% endif %}\n {% endif %}\n\n {% mandatory_gates %}\n </zl-card>\n</zl-page-shell>\n";
9510
9679
  //#endregion
9511
9680
  //#region src/orchestrator/liquid.ts
9512
9681
  /**
@@ -9525,6 +9694,21 @@ var default_default = "<zl-page-shell data-zl-template-root>\n {% if branding.l
9525
9694
  * - Partials are loaded from an in-memory map; no filesystem access.
9526
9695
  */
9527
9696
  const TEMPLATE_NAMES$1 = { default: "default" };
9697
+ /**
9698
+ * Maps `error.*` text keys to a field name (Figma inline-error
9699
+ * annotations). Shared by the template filters (`fieldError` routes these
9700
+ * inline, `formLevelError` suppresses their banner) and by
9701
+ * {@link localiseFlowErrorKeys}, which must downgrade them to a banner
9702
+ * message when the step doesn't render the mapped field.
9703
+ */
9704
+ const fieldErrorKeys = {
9705
+ "error.email_required": "email",
9706
+ "error.email_invalid": "email",
9707
+ "error.email_exists": "email",
9708
+ "error.password_required": "password",
9709
+ "error.password_incorrect": "password",
9710
+ "error.invalid_credentials": "password"
9711
+ };
9528
9712
  function createLiquidEngine(options) {
9529
9713
  const engine = new Liquid({
9530
9714
  templates: {
@@ -9541,7 +9725,7 @@ function createLiquidEngine(options) {
9541
9725
  engine.registerFilter("raw", (value) => stringify(value));
9542
9726
  engine.registerFilter("t", function tFilter(key, ...args) {
9543
9727
  const lookupKey = stringify(key);
9544
- return interpolate(options.locale[lookupKey] ?? lookupKey, args.map(stringify));
9728
+ return interpolate(options.locale[lookupKey] ?? injectedKeyFallback(options.locale, lookupKey) ?? lookupKey, args.map(stringify));
9545
9729
  });
9546
9730
  /** Resolves `{text_key}.placeholder` — empty when undefined (not the raw key). */
9547
9731
  engine.registerFilter("fieldPlaceholder", (textKey) => {
@@ -9569,15 +9753,6 @@ function createLiquidEngine(options) {
9569
9753
  };
9570
9754
  });
9571
9755
  });
9572
- /** Maps `error.*` text keys to a field name (Figma inline-error annotations). */
9573
- const fieldErrorKeys = {
9574
- "error.email_required": "email",
9575
- "error.email_invalid": "email",
9576
- "error.email_exists": "email",
9577
- "error.password_required": "password",
9578
- "error.password_incorrect": "password",
9579
- "error.invalid_credentials": "password"
9580
- };
9581
9756
  /** Resolves `{text_key}.title` for form-level `<zl-alert heading>`. */
9582
9757
  engine.registerFilter("alertHeading", (textKey) => {
9583
9758
  const lookupKey = `${stringify(textKey)}.title`;
@@ -9611,6 +9786,105 @@ function createLiquidEngine(options) {
9611
9786
  });
9612
9787
  return engine;
9613
9788
  }
9789
+ /**
9790
+ * Fallbacks for text keys the flow engine derives from tenant-chosen step
9791
+ * names (`<step>.action.back` for the injected back action — see
9792
+ * `internal/domain/flow_state_machine.go` `buildStep`). Step names are open,
9793
+ * so no dictionary can enumerate them; a missing step-specific key falls back
9794
+ * to its generic entry instead of leaking the raw key into the UI.
9795
+ */
9796
+ const INJECTED_KEY_FALLBACKS = [{
9797
+ suffix: ".action.back",
9798
+ fallback: "action.back"
9799
+ }];
9800
+ function injectedKeyFallback(locale, key) {
9801
+ for (const { suffix, fallback } of INJECTED_KEY_FALLBACKS) if (key.endsWith(suffix)) return locale[fallback];
9802
+ }
9803
+ /**
9804
+ * Rule-suffix fallbacks for the server's field-validation keys
9805
+ * (`error.<field>_<rule>` — see `FlowFieldValidationError.TextKey` in
9806
+ * `internal/domain/flow_field_resolver.go`). Field names come from the
9807
+ * tenant's user schema, so no catalog can enumerate the specific keys;
9808
+ * a miss resolves to the rule's generic entry, interpolated with the
9809
+ * field's label (`{0}`). The server spells the format rule `_invalid`
9810
+ * (the catalog's existing convention, e.g. `error.email_invalid`), so
9811
+ * that suffix takes the format wording; `_unknown_field` (a submitted
9812
+ * name that is not a step field) takes the catch-all.
9813
+ */
9814
+ const FLOW_ERROR_RULE_FALLBACKS = [
9815
+ {
9816
+ suffix: "_required",
9817
+ generic: "error.field_required"
9818
+ },
9819
+ {
9820
+ suffix: "_min_length",
9821
+ generic: "error.field_min_length"
9822
+ },
9823
+ {
9824
+ suffix: "_max_length",
9825
+ generic: "error.field_max_length"
9826
+ },
9827
+ {
9828
+ suffix: "_format",
9829
+ generic: "error.field_format"
9830
+ },
9831
+ {
9832
+ suffix: "_invalid",
9833
+ generic: "error.field_format"
9834
+ },
9835
+ {
9836
+ suffix: "_unknown_field",
9837
+ generic: "error.field_invalid"
9838
+ }
9839
+ ];
9840
+ const FLOW_ERROR_CATCH_ALL_KEY = "error.field_invalid";
9841
+ /**
9842
+ * Localises a `step.error` payload of field-validation text keys.
9843
+ * Returns `null` unless EVERY `"; "`-joined segment is an `error.*` key
9844
+ * — the caller keeps other payloads (outcome names, `auth_attempt.*`
9845
+ * diagnostics) verbatim.
9846
+ *
9847
+ * Keys the locale knows pass through as `text_key` entries: the template
9848
+ * localises them via `| t`, and `fieldErrorKeys` routes the known ones
9849
+ * inline to their field. Unknown keys with a recognised rule suffix are
9850
+ * pre-localised here from their generic {@link FLOW_ERROR_RULE_FALLBACKS}
9851
+ * entry; unknown keys without one pass through as `text_key` (matching
9852
+ * `| t`'s behaviour for non-validation keys such as
9853
+ * `error.sign_in_server`, which localises via `.title`/`.body`).
9854
+ */
9855
+ function localiseFlowErrorKeys(raw, ctx) {
9856
+ const segments = raw.split("; ");
9857
+ if (!segments.every((segment) => segment.startsWith("error."))) return null;
9858
+ return segments.map((key) => localiseFlowErrorKey(key, ctx));
9859
+ }
9860
+ function localiseFlowErrorKey(key, ctx) {
9861
+ const inlineField = fieldErrorKeys[key];
9862
+ const orphanedInline = inlineField !== void 0 && ctx.fields !== void 0 && !ctx.fields.includes(inlineField);
9863
+ if (!orphanedInline && ctx.locale[key] !== void 0) return { text_key: key };
9864
+ for (const { suffix, generic } of FLOW_ERROR_RULE_FALLBACKS) {
9865
+ if (!key.endsWith(suffix)) continue;
9866
+ const field = key.slice(6, key.length - suffix.length);
9867
+ if (field === "") break;
9868
+ return { message: capitaliseFirst(interpolate(ctx.locale[generic] ?? ctx.locale[FLOW_ERROR_CATCH_ALL_KEY] ?? "Please check {0}.", [fieldLabel(ctx, field)])) };
9869
+ }
9870
+ if (orphanedInline && ctx.locale[key] !== void 0) return { message: ctx.locale[key] };
9871
+ return { text_key: key };
9872
+ }
9873
+ /** `<step>.field.<name>` from the locale, else a humanised field name. */
9874
+ function fieldLabel(ctx, field) {
9875
+ return ctx.locale[`${ctx.stepName}.field.${field}`] ?? humaniseFieldName(field);
9876
+ }
9877
+ /**
9878
+ * `x-auth-methods#password` → "password", `givenName` → "given name",
9879
+ * `date_of_birth` → "date of birth".
9880
+ */
9881
+ function humaniseFieldName(field) {
9882
+ return (field.includes("#") ? field.split("#").pop() : field).replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_.-]+/g, " ").trim().toLowerCase();
9883
+ }
9884
+ /** Messages may open with a lowercase field label; sentence-case them. */
9885
+ function capitaliseFirst(text) {
9886
+ return text.length > 0 ? text[0]?.toUpperCase() + text.slice(1) : text;
9887
+ }
9614
9888
  function stringify(value) {
9615
9889
  if (value == null) return "";
9616
9890
  if (typeof value === "string") return value;
@@ -9621,6 +9895,11 @@ function stringify(value) {
9621
9895
  return String(value);
9622
9896
  }
9623
9897
  }
9898
+ /**
9899
+ * Positional `{n}` substitution shared by the `| t` filter and the
9900
+ * pre-localising fallback in {@link localiseFlowErrorKeys}, so both
9901
+ * produce byte-identical output for the same template and args.
9902
+ */
9624
9903
  function interpolate(template, args) {
9625
9904
  if (args.length === 0) return template;
9626
9905
  return template.replace(/\{(\d+)\}/g, (match, index) => {
@@ -9738,6 +10017,10 @@ const en = {
9738
10017
  "signed-in.continue": "Continue",
9739
10018
  "signed-in.logout": "Sign out",
9740
10019
  "passkey-login.title": "Sign in with your passkey",
10020
+ "passkey-first.title": "Sign in",
10021
+ "passkey-first.description": "Use your passkey to continue",
10022
+ "passkey-first.action.passkey": "Continue with passkey",
10023
+ "passkey-first.action.email_fallback": "Use email instead",
9741
10024
  "recover.title": "Check your email",
9742
10025
  "recover.description": "We sent a password reset link to your email address.",
9743
10026
  "recover.action.back": "Back to sign in",
@@ -9745,8 +10028,11 @@ const en = {
9745
10028
  "submit.signin": "Sign in",
9746
10029
  "action.forgot_password": "Forgot password?",
9747
10030
  "action.cancel": "Cancel",
10031
+ "action.back": "Back",
9748
10032
  "sso.redirect.title": "Redirecting to your provider…",
9749
- "error.passkey_cancelled": "Passkey setup was cancelled",
10033
+ "passkey.pending.status": "Waiting for your passkey…",
10034
+ "error.passkey_cancelled": "The passkey prompt was closed before completing.",
10035
+ "error.passkey_timeout": "The passkey request timed out. Please try again.",
9750
10036
  "error.passkey_not_registered": "This passkey is not registered. Please sign in with your email and password.",
9751
10037
  "error.passkey_setup_failed": "Passkey registration did not complete. Please try again.",
9752
10038
  "error.passkey_unsupported": "This device does not support passkeys",
@@ -9759,6 +10045,11 @@ const en = {
9759
10045
  /** Figma sign-in error `6602:180268` — inline on password field. */
9760
10046
  "error.invalid_credentials": "Wrong email or password.",
9761
10047
  "error.required": "This field is required.",
10048
+ "error.field_required": "{0} is required.",
10049
+ "error.field_format": "Please enter a valid {0}.",
10050
+ "error.field_min_length": "{0} is too short.",
10051
+ "error.field_max_length": "{0} is too long.",
10052
+ "error.field_invalid": "Please check {0}.",
9762
10053
  "error.sign_in_server.title": "We couldn't complete your sign in.",
9763
10054
  "error.sign_in_server.body": "Please try again in a few minutes"
9764
10055
  };
@@ -9840,6 +10131,10 @@ const de = {
9840
10131
  "signed-in.continue": "Weiter",
9841
10132
  "signed-in.logout": "Abmelden",
9842
10133
  "passkey-login.title": "Mit Passkey anmelden",
10134
+ "passkey-first.title": "Anmelden",
10135
+ "passkey-first.description": "Verwende deinen Passkey, um fortzufahren",
10136
+ "passkey-first.action.passkey": "Weiter mit Passkey",
10137
+ "passkey-first.action.email_fallback": "Stattdessen E-Mail verwenden",
9843
10138
  "recover.title": "E-Mail prüfen",
9844
10139
  "recover.description": "Wir haben einen Link zum Zurücksetzen des Passworts an deine E-Mail-Adresse gesendet.",
9845
10140
  "recover.action.back": "Zurück zur Anmeldung",
@@ -9847,8 +10142,11 @@ const de = {
9847
10142
  "submit.signin": "Anmelden",
9848
10143
  "action.forgot_password": "Passwort vergessen?",
9849
10144
  "action.cancel": "Abbrechen",
10145
+ "action.back": "Zurück",
9850
10146
  "sso.redirect.title": "Weiterleitung zum Anbieter…",
9851
- "error.passkey_cancelled": "Passkey-Einrichtung wurde abgebrochen",
10147
+ "passkey.pending.status": "Warten auf deinen Passkey…",
10148
+ "error.passkey_cancelled": "Die Passkey-Abfrage wurde vorzeitig geschlossen.",
10149
+ "error.passkey_timeout": "Die Passkey-Anfrage hat zu lange gedauert. Bitte versuche es erneut.",
9852
10150
  "error.passkey_not_registered": "Dieser Passkey ist nicht registriert. Bitte melde dich mit E-Mail und Passwort an.",
9853
10151
  "error.passkey_setup_failed": "Passkey-Registrierung wurde nicht abgeschlossen. Bitte versuche es erneut.",
9854
10152
  "error.passkey_unsupported": "Dieses Gerät unterstützt keine Passkeys",
@@ -9860,6 +10158,11 @@ const de = {
9860
10158
  "error.email_exists": "Ein Konto mit dieser E-Mail-Adresse existiert bereits.",
9861
10159
  "error.invalid_credentials": "Falsche E-Mail oder falsches Passwort.",
9862
10160
  "error.required": "Dieses Feld ist erforderlich.",
10161
+ "error.field_required": "{0} ist erforderlich.",
10162
+ "error.field_format": "Bitte gib einen gültigen Wert für {0} ein.",
10163
+ "error.field_min_length": "{0} ist zu kurz.",
10164
+ "error.field_max_length": "{0} ist zu lang.",
10165
+ "error.field_invalid": "Bitte überprüfe {0}.",
9863
10166
  "error.sign_in_server.title": "Anmeldung konnte nicht abgeschlossen werden.",
9864
10167
  "error.sign_in_server.body": "Bitte versuche es in einigen Minuten erneut"
9865
10168
  };
@@ -9944,6 +10247,10 @@ const builtinLocales = {
9944
10247
  "signed-in.continue": "Continua",
9945
10248
  "signed-in.logout": "Esci",
9946
10249
  "passkey-login.title": "Accedi con la tua passkey",
10250
+ "passkey-first.title": "Accedi",
10251
+ "passkey-first.description": "Usa la tua passkey per continuare",
10252
+ "passkey-first.action.passkey": "Continua con passkey",
10253
+ "passkey-first.action.email_fallback": "Usa invece l'email",
9947
10254
  "recover.title": "Controlla la tua e-mail",
9948
10255
  "recover.description": "Abbiamo inviato un link per reimpostare la password al tuo indirizzo e-mail.",
9949
10256
  "recover.action.back": "Torna all'accesso",
@@ -9951,8 +10258,11 @@ const builtinLocales = {
9951
10258
  "submit.signin": "Accedi",
9952
10259
  "action.forgot_password": "Password dimenticata?",
9953
10260
  "action.cancel": "Annulla",
10261
+ "action.back": "Indietro",
9954
10262
  "sso.redirect.title": "Reindirizzamento al provider…",
9955
- "error.passkey_cancelled": "La configurazione della passkey è stata annullata",
10263
+ "passkey.pending.status": "In attesa della tua passkey",
10264
+ "error.passkey_cancelled": "La richiesta della passkey è stata chiusa prima del completamento.",
10265
+ "error.passkey_timeout": "La richiesta della passkey è scaduta. Riprova.",
9956
10266
  "error.passkey_not_registered": "Questa passkey non è registrata. Accedi con e-mail e password.",
9957
10267
  "error.passkey_setup_failed": "La registrazione della passkey non è stata completata. Riprova.",
9958
10268
  "error.passkey_unsupported": "Questo dispositivo non supporta le passkey",
@@ -9964,6 +10274,11 @@ const builtinLocales = {
9964
10274
  "error.email_exists": "Esiste già un account con questo indirizzo e-mail.",
9965
10275
  "error.invalid_credentials": "E-mail o password errata.",
9966
10276
  "error.required": "Questo campo è obbligatorio.",
10277
+ "error.field_required": "{0} è obbligatorio.",
10278
+ "error.field_format": "Inserisci un valore valido per {0}.",
10279
+ "error.field_min_length": "{0} è troppo corto.",
10280
+ "error.field_max_length": "{0} è troppo lungo.",
10281
+ "error.field_invalid": "Controlla {0}.",
9967
10282
  "error.sign_in_server.title": "Non è stato possibile completare l'accesso.",
9968
10283
  "error.sign_in_server.body": "Riprova tra qualche minuto"
9969
10284
  }
@@ -11897,6 +12212,13 @@ let ZitadelLogin = class ZitadelLogin extends i$2 {
11897
12212
  set purpose(value) {
11898
12213
  this.#_purpose_accessor_storage = value;
11899
12214
  }
12215
+ #_flowName_accessor_storage = "";
12216
+ get flowName() {
12217
+ return this.#_flowName_accessor_storage;
12218
+ }
12219
+ set flowName(value) {
12220
+ this.#_flowName_accessor_storage = value;
12221
+ }
11900
12222
  #_project_accessor_storage;
11901
12223
  get project() {
11902
12224
  return this.#_project_accessor_storage;
@@ -12004,6 +12326,8 @@ let ZitadelLogin = class ZitadelLogin extends i$2 {
12004
12326
  sheet.replaceSync(layout_chrome_default);
12005
12327
  root.adoptedStyleSheets = [...existing, sheet];
12006
12328
  root.addEventListener("zl-input", this.handleAtomInput);
12329
+ root.addEventListener("zl-change", this.handleAtomEdited);
12330
+ root.addEventListener("zl-dismiss", this.handleAlertDismiss);
12007
12331
  root.addEventListener("zl-submit", this.handleAtomSubmit);
12008
12332
  root.addEventListener("click", this.handleDelegatedAction);
12009
12333
  root.addEventListener("submit", this.handleFormSubmit);
@@ -12142,17 +12466,32 @@ let ZitadelLogin = class ZitadelLogin extends i$2 {
12142
12466
  if (!cfg.projectId) throw new Error("<zitadel-login> requires a project id (the `project-id` attribute, `configureZitadel({ projectId })`, or a `project` handle) to start a flow.");
12143
12467
  wire = await startFlow(api, {
12144
12468
  project_id: cfg.projectId,
12145
- purpose: this.purpose
12469
+ purpose: this.purpose,
12470
+ ...this.flowName ? { flow_definition_name: this.flowName } : {}
12146
12471
  });
12147
12472
  }
12148
12473
  this.applyResponse(wire);
12149
12474
  } catch (error) {
12150
- this.handleTransportError(error);
12475
+ this.handleTransportError(this.describeFlowSelectionError(error));
12151
12476
  } finally {
12152
12477
  this.loading = false;
12153
12478
  }
12154
12479
  }
12480
+ /**
12481
+ * When a `flow-name` lookup fails, the server's envelope only says
12482
+ * "not found" / "purpose mismatch" — it cannot know the name came from
12483
+ * an attribute. Rewrap those two codes with the attribute and the fix;
12484
+ * every other error passes through untouched.
12485
+ */
12486
+ describeFlowSelectionError(error) {
12487
+ if (!this.flowName || !(error instanceof ApiError)) return error;
12488
+ const code = typeof error.body === "object" && error.body !== null && "code" in error.body ? String(error.body.code) : "";
12489
+ if (code === "flowdef.not_found") return /* @__PURE__ */ new Error(`<zitadel-login> flow-name="${this.flowName}" does not match any active flow definition in this project. Check the \`name\` in your flow file and that it has been applied (\`zitadel apply\`).`);
12490
+ if (code === "flowdef.purpose_mismatch") return /* @__PURE__ */ new Error(`<zitadel-login> flow-name="${this.flowName}" matched a flow definition that does not serve purpose "${this.purpose}".`);
12491
+ return error;
12492
+ }
12155
12493
  applyResponse(wire) {
12494
+ this.stepErrorDismissed = false;
12156
12495
  this.response = wire;
12157
12496
  const { branding, issues } = validateBranding(wire.branding);
12158
12497
  this.branding = branding;
@@ -12206,7 +12545,12 @@ let ZitadelLogin = class ZitadelLogin extends i$2 {
12206
12545
  }
12207
12546
  renderStep(step, engine) {
12208
12547
  const tenantSource = typeof this.branding?.liquid_template === "string" && this.branding.liquid_template.length > 0 ? this.branding.liquid_template : null;
12209
- const errors = step.error ? step.error.startsWith("error.") ? [{ text_key: step.error }] : [{ message: step.error }] : [];
12548
+ const rawErrors = step.error ? localiseFlowErrorKeys(step.error, {
12549
+ locale: this.resolveLocale(),
12550
+ stepName: step.name ?? "",
12551
+ fields: (step.fields ?? []).map((field) => field.name)
12552
+ }) ?? [{ message: step.error }] : [];
12553
+ const errors = this.loading && this.stepErrorDismissed ? [] : rawErrors;
12210
12554
  const fields = step.fields ?? [];
12211
12555
  const actions = step.actions ?? [];
12212
12556
  const context = {
@@ -12300,6 +12644,17 @@ let ZitadelLogin = class ZitadelLogin extends i$2 {
12300
12644
  if (native && native.value !== field.value) return native.value;
12301
12645
  return typeof field.value === "string" ? field.value : void 0;
12302
12646
  }
12647
+ /**
12648
+ * True once the user retired the current step error by editing a field or
12649
+ * dismissing the alert. Deliberately NON-reactive: consulting reactive
12650
+ * state in `renderStep` would change its output string on the first
12651
+ * post-dismiss keystroke, and `unsafeHTML` would rebuild the whole step
12652
+ * subtree — wiping typed values (`hydrateStepAfterRender` only re-applies
12653
+ * them on response changes) and reconnecting atoms. Reset on every new
12654
+ * response; consulted only by the `loading` re-render, which rebuilds
12655
+ * anyway.
12656
+ */
12657
+ stepErrorDismissed = false;
12303
12658
  handleAtomInput = (event) => {
12304
12659
  if (!event.detail) return;
12305
12660
  const { name, value } = event.detail;
@@ -12309,11 +12664,41 @@ let ZitadelLogin = class ZitadelLogin extends i$2 {
12309
12664
  [name]: value
12310
12665
  };
12311
12666
  this.syncFieldElementValue(name, value);
12667
+ this.clearStaleErrors(name);
12312
12668
  emit(this, "zitadel-flow-input", {
12313
12669
  name,
12314
12670
  value
12315
12671
  });
12316
12672
  };
12673
+ /** `zl-change` from <zl-checkbox>/<zl-select>: only error clearing. */
12674
+ handleAtomEdited = (event) => {
12675
+ const name = event.detail?.name;
12676
+ if (name) this.clearStaleErrors(name);
12677
+ };
12678
+ /** Explicit dismiss of the step-error alert (it removes itself). */
12679
+ handleAlertDismiss = (event) => {
12680
+ if (event.target?.matches?.("zl-alert[data-zl-step-error]")) this.stepErrorDismissed = true;
12681
+ };
12682
+ /**
12683
+ * Retire the current step error after the user edits `fieldName`:
12684
+ * remove the form-level alert(s) and clear the edited field's inline
12685
+ * error — other fields' inline errors stay until they are edited.
12686
+ * Imperative on purpose; see {@link stepErrorDismissed}.
12687
+ */
12688
+ clearStaleErrors(fieldName) {
12689
+ if (!this.response?.step.error) return;
12690
+ const root = this.shadowRoot;
12691
+ if (!root) return;
12692
+ if (!this.stepErrorDismissed) {
12693
+ this.stepErrorDismissed = true;
12694
+ for (const alert of root.querySelectorAll("zl-alert[data-zl-step-error]")) alert.remove();
12695
+ }
12696
+ for (const field of root.querySelectorAll("zl-field")) {
12697
+ if (field.getAttribute("name") !== fieldName || !field.invalid) continue;
12698
+ field.invalid = false;
12699
+ field.error = "";
12700
+ }
12701
+ }
12317
12702
  syncFieldElementValue(name, value) {
12318
12703
  const root = this.shadowRoot;
12319
12704
  if (!root) return;
@@ -12373,9 +12758,10 @@ let ZitadelLogin = class ZitadelLogin extends i$2 {
12373
12758
  */
12374
12759
  handlePasskeyError = (event) => {
12375
12760
  if (!this.response) return;
12376
- const { error: message, aborted } = event.detail;
12377
- const errorKey = aborted ? "error.passkey_cancelled" : "error.passkey_failed";
12761
+ const { error: message, aborted, timed_out: timedOut } = event.detail;
12762
+ const errorKey = timedOut ? "error.passkey_timeout" : aborted ? "error.passkey_cancelled" : "error.passkey_failed";
12378
12763
  if (this.response.step.error === errorKey) return;
12764
+ this.stepErrorDismissed = false;
12379
12765
  const { challenge: _dropped, ...stepWithoutChallenge } = this.response.step;
12380
12766
  this.response = {
12381
12767
  ...this.response,
@@ -12384,7 +12770,7 @@ let ZitadelLogin = class ZitadelLogin extends i$2 {
12384
12770
  error: errorKey
12385
12771
  }
12386
12772
  };
12387
- console.warn(`[zitadel-login] passkey ceremony ${aborted ? "cancelled" : "failed"}: ${message}`);
12773
+ console.warn(`[zitadel-login] passkey ceremony ${timedOut ? "timed out" : aborted ? "cancelled" : "failed"}: ${message}`);
12388
12774
  };
12389
12775
  findPrimaryAction() {
12390
12776
  const root = this.shadowRoot;
@@ -12426,13 +12812,17 @@ let ZitadelLogin = class ZitadelLogin extends i$2 {
12426
12812
  }
12427
12813
  }
12428
12814
  handleTransportError(error) {
12429
- const message = error instanceof Error ? error.message : "Unexpected error contacting the Flow API.";
12815
+ const message = error instanceof ApiError ? apiErrorMessage(error) : error instanceof Error ? error.message : "Unexpected error contacting the Flow API.";
12430
12816
  this.startupError = message;
12431
12817
  console.error("[zitadel-login]", error);
12432
12818
  emit(this, "zitadel-flow-error", { message });
12433
12819
  }
12434
12820
  };
12435
12821
  __decorate([n$1({ type: String })], ZitadelLogin.prototype, "purpose", null);
12822
+ __decorate([n$1({
12823
+ type: String,
12824
+ attribute: "flow-name"
12825
+ })], ZitadelLogin.prototype, "flowName", null);
12436
12826
  __decorate([n$1({ attribute: false })], ZitadelLogin.prototype, "project", null);
12437
12827
  __decorate([n$1({
12438
12828
  type: String,