@zitadel/components 1.0.0-alpha.21 → 1.0.0-alpha.23

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.
@@ -681,7 +681,7 @@ const e$5 = (e, t, c) => (c.configurable = !0, c.enumerable = !0, Reflect.decora
681
681
  }
682
682
  //#endregion
683
683
  //#region src/atoms/zl-alert.css?inline
684
- var zl_alert_default = ":host {\n display: block;\n}\n\n.zr-alert {\n align-items: flex-start;\n gap: var(--zl-spacing-2);\n padding-block: var(--zl-spacing-3);\n padding-inline: var(--zl-spacing-4);\n background: var(--zl-muted);\n color: var(--zl-foreground);\n border: 1px solid var(--zl-border);\n border-radius: var(--zl-radius-lg);\n font-family: var(--zl-font-family-sans);\n font-size: var(--zl-text-sm-size);\n line-height: var(--zl-text-sm-leading);\n display: flex;\n}\n\n.zr-alert__icon {\n color: currentColor;\n flex-shrink: 0;\n padding-block-start: var(--zl-spacing-0-5);\n}\n\n.zr-alert__body {\n gap: var(--zl-spacing-0-5);\n flex-direction: column;\n flex: auto;\n min-width: 0;\n display: flex;\n}\n\n.zr-alert__title {\n font-family: var(--zl-font-family-heading);\n color: var(--zl-foreground);\n}\n\n.zr-alert__message {\n color: var(--zl-muted-foreground);\n margin: 0;\n}\n\n.zr-alert:not(:has(.zr-alert__title)) .zr-alert__message {\n color: var(--zl-foreground);\n}\n\n.zr-alert--error, .zr-alert--error .zr-alert__title {\n color: var(--zl-destructive);\n}\n\n.zr-alert--success .zr-alert__icon {\n color: var(--zl-success);\n}\n\n.zr-alert--warning .zr-alert__icon, .zr-alert--info .zr-alert__icon {\n color: var(--zl-muted-foreground);\n}\n\n.zr-alert__close {\n all: unset;\n width: var(--zl-spacing-6);\n height: var(--zl-spacing-6);\n border-radius: var(--zl-radius-md);\n color: var(--zl-muted-foreground);\n cursor: pointer;\n transition: background-color var(--zl-duration-fast) var(--zl-easing-standard);\n flex-shrink: 0;\n justify-content: center;\n align-items: center;\n display: inline-flex;\n}\n\n.zr-alert__close .zr-icon {\n --zl-icon-size: var(--zl-spacing-4);\n width: var(--zl-spacing-4);\n height: var(--zl-spacing-4);\n}\n\n.zr-alert__close:hover {\n background: var(--zl-accent);\n color: var(--zl-accent-foreground);\n}\n\n.zr-alert__close:focus-visible {\n outline: var(--zl-focus-width) solid var(--zl-ring);\n outline-offset: var(--zl-focus-offset);\n}\n";
684
+ var zl_alert_default = ":host {\n display: block;\n}\n\n.zr-alert {\n align-items: flex-start;\n gap: var(--zl-spacing-2);\n padding-block: var(--zl-spacing-3);\n padding-inline: var(--zl-spacing-4);\n background: var(--zl-muted);\n color: var(--zl-foreground);\n border: 1px solid var(--zl-border);\n border-radius: var(--zl-radius-lg);\n font-family: var(--zl-font-family-sans);\n font-size: var(--zl-text-sm-size);\n line-height: var(--zl-text-sm-leading);\n display: flex;\n}\n\n.zr-alert__icon {\n height: var(--zl-text-sm-leading);\n color: currentColor;\n flex-shrink: 0;\n align-items: center;\n display: inline-flex;\n}\n\n.zr-alert__body {\n gap: var(--zl-spacing-0-5);\n flex-direction: column;\n flex: auto;\n min-width: 0;\n display: flex;\n}\n\n.zr-alert__title {\n font-family: var(--zl-font-family-heading);\n color: var(--zl-foreground);\n}\n\n.zr-alert__message {\n color: var(--zl-muted-foreground);\n margin: 0;\n}\n\n.zr-alert:not(:has(.zr-alert__title)) .zr-alert__message {\n color: var(--zl-foreground);\n}\n\n.zr-alert--error {\n color: var(--zl-destructive);\n}\n\n.zr-alert--success .zr-alert__icon {\n color: var(--zl-success);\n}\n\n.zr-alert--warning .zr-alert__icon, .zr-alert--info .zr-alert__icon {\n color: var(--zl-muted-foreground);\n}\n\n.zr-alert__close {\n all: unset;\n width: var(--zl-spacing-6);\n height: var(--zl-spacing-6);\n border-radius: var(--zl-radius-md);\n color: var(--zl-muted-foreground);\n cursor: pointer;\n transition: background-color var(--zl-duration-fast) var(--zl-easing-standard);\n flex-shrink: 0;\n justify-content: center;\n align-items: center;\n display: inline-flex;\n}\n\n.zr-alert__close zl-icon {\n flex-shrink: 0;\n display: inline-flex;\n}\n\n.zr-alert__close:hover {\n background: var(--zl-accent);\n color: var(--zl-accent-foreground);\n}\n\n.zr-alert__close:focus-visible {\n outline: var(--zl-focus-width) solid var(--zl-ring);\n outline-offset: var(--zl-focus-offset);\n}\n";
685
685
  //#endregion
686
686
  //#region src/internal/emit.ts
687
687
  /**
@@ -2638,6 +2638,7 @@ let ZlField = class ZlField extends i$2 {
2638
2638
  class="zr-field__trailing-action"
2639
2639
  part="trailing-action"
2640
2640
  aria-label="Clear"
2641
+ tabindex="-1"
2641
2642
  ?disabled=${this.disabled}
2642
2643
  @click=${this.handleClear}
2643
2644
  >
@@ -4023,18 +4024,28 @@ function validateBranding(input, context = {}) {
4023
4024
  out.layout = CreateFlow201BrandingLayout.centered;
4024
4025
  }
4025
4026
  out.logo_url = sanitiseUrl(out.logo_url, "logo_url", issues, { allowed: allowLoopbackHttp });
4026
- out.font_url = sanitiseUrl(out.font_url, "font_url", issues);
4027
+ if (out.typography?.font_url) out.typography = {
4028
+ ...out.typography,
4029
+ font_url: sanitiseUrl(out.typography.font_url, "typography.font_url", issues)
4030
+ };
4027
4031
  out.hero_url = sanitiseUrl(out.hero_url, "hero_url", issues, { allowed: allowLoopbackHttp });
4028
- if (out.assets) out.assets = {
4029
- logo_dark: sanitiseUrl(out.assets.logo_dark, "assets.logo_dark", issues),
4030
- favicon: sanitiseUrl(out.assets.favicon, "assets.favicon", issues),
4031
- background_image: sanitiseUrl(out.assets.background_image, "assets.background_image", issues)
4032
+ if (out.theme?.light || out.theme?.dark) out.theme = {
4033
+ ...out.theme,
4034
+ ...sanitiseSideLogo(out.theme.light, "light", issues, allowLoopbackHttp),
4035
+ ...sanitiseSideLogo(out.theme.dark, "dark", issues, allowLoopbackHttp)
4032
4036
  };
4033
4037
  return {
4034
4038
  branding: out,
4035
4039
  issues
4036
4040
  };
4037
4041
  }
4042
+ function sanitiseSideLogo(side, name, issues, allowLoopbackHttp) {
4043
+ if (!side) return {};
4044
+ return { [name]: {
4045
+ ...side,
4046
+ logo_url: sanitiseUrl(side.logo_url, `theme.${name}.logo_url`, issues, { allowed: allowLoopbackHttp })
4047
+ } };
4048
+ }
4038
4049
  function sanitiseUrl(value, field, issues, loopbackHttp) {
4039
4050
  if (value == null || value === "") return;
4040
4051
  try {
@@ -4052,6 +4063,34 @@ function sanitiseUrl(value, field, issues, loopbackHttp) {
4052
4063
  }
4053
4064
  }
4054
4065
  //#endregion
4066
+ //#region src/orchestrator/branding.ts
4067
+ /**
4068
+ * Which sides the revision actually publishes. A side is published when the
4069
+ * revision names it at all — an empty side object still says "this surface is
4070
+ * mine", it just takes the maintained defaults for every key.
4071
+ */
4072
+ function publishedSides(branding) {
4073
+ const theme = branding?.theme;
4074
+ const sides = [];
4075
+ if (theme?.light) sides.push("light");
4076
+ if (theme?.dark) sides.push("dark");
4077
+ return sides;
4078
+ }
4079
+ /**
4080
+ * The mark for the resolved surface. A logo is pixels and is never recoloured,
4081
+ * so a side without its own file shows no mark rather than borrowing the other
4082
+ * side's. The legacy top-level `logo_url` is a single-mark fallback and applies
4083
+ * only when neither side names one.
4084
+ */
4085
+ function resolveLogoUrl(branding, theme) {
4086
+ if (!branding) return void 0;
4087
+ const sides = branding.theme;
4088
+ const sideLogo = theme === "light" ? sides?.light?.logo_url : sides?.dark?.logo_url;
4089
+ if (sideLogo) return sideLogo;
4090
+ if (sides?.light?.logo_url || sides?.dark?.logo_url) return void 0;
4091
+ return branding.logo_url;
4092
+ }
4093
+ //#endregion
4055
4094
  //#region src/manifests.ts
4056
4095
  /**
4057
4096
  * Aggregate registry of every shipped `<zl-*>` atom's manifest.
@@ -6982,7 +7021,7 @@ const minus = argumentsToNumber((v, arg) => v - arg);
6982
7021
  const plus = argumentsToNumber((lhs, rhs) => lhs + rhs);
6983
7022
  const modulo = argumentsToNumber((v, arg) => (v % arg + arg) % arg);
6984
7023
  const times = argumentsToNumber((v, arg) => v * arg);
6985
- function round(v, arg = 0) {
7024
+ function round$1(v, arg = 0) {
6986
7025
  v = toNumber(v);
6987
7026
  arg = toNumber(arg);
6988
7027
  const amp = Math.pow(10, arg);
@@ -7001,7 +7040,7 @@ var mathFilters = /* @__PURE__ */ Object.freeze({
7001
7040
  plus,
7002
7041
  modulo,
7003
7042
  times,
7004
- round
7043
+ round: round$1
7005
7044
  });
7006
7045
  const url_decode = (x) => decodeURIComponent(stringify$1(x)).replace(/\+/g, " ");
7007
7046
  const url_encode = (x) => encodeURIComponent(stringify$1(x)).replace(/%20/g, "+");
@@ -9039,7 +9078,7 @@ const en = {
9039
9078
  "password.action.register.lead": "Don't have an account? ",
9040
9079
  "password.action.register.link": "Sign up",
9041
9080
  "register.title": "Create your account",
9042
- "register.description": "",
9081
+ "register.description": "Enter your details to get started",
9043
9082
  "register.field.email": "Email",
9044
9083
  "register.field.email.placeholder": "you@example.com",
9045
9084
  "register.field.password": "Password",
@@ -9087,7 +9126,7 @@ const en = {
9087
9126
  "error.password_required": "Please enter a password",
9088
9127
  "error.password_incorrect": "Wrong email or password.",
9089
9128
  "error.email_exists": "An account with this email already exists.",
9090
- /** Figma sign-in error `6602:180268` — inline on password field. */
9129
+ /** Sign-in error, inline on the password field. */
9091
9130
  "error.invalid_credentials": "Wrong email or password.",
9092
9131
  "error.required": "This field is required.",
9093
9132
  "error.field_required": "{0} is required.",
@@ -9156,7 +9195,7 @@ const de = {
9156
9195
  "password.action.register.lead": "Noch kein Konto? ",
9157
9196
  "password.action.register.link": "Registrieren",
9158
9197
  "register.title": "Konto erstellen",
9159
- "register.description": "",
9198
+ "register.description": "Gib deine Daten ein, um loszulegen",
9160
9199
  "register.field.email": "E-Mail",
9161
9200
  "register.field.email.placeholder": "du@beispiel.de",
9162
9201
  "register.field.password": "Passwort",
@@ -9272,7 +9311,7 @@ const it = {
9272
9311
  "password.action.register.lead": "Non hai un account? ",
9273
9312
  "password.action.register.link": "Registrati",
9274
9313
  "register.title": "Crea il tuo account",
9275
- "register.description": "",
9314
+ "register.description": "Inserisci i tuoi dati per iniziare",
9276
9315
  "register.field.email": "E-mail",
9277
9316
  "register.field.email.placeholder": "tu@esempio.com",
9278
9317
  "register.field.password": "Password",
@@ -9410,22 +9449,24 @@ var zitadelNextGen_exports = /* @__PURE__ */ __exportAll({
9410
9449
  createFlowDefinition: () => createFlowDefinition,
9411
9450
  createGrant: () => createGrant,
9412
9451
  createHandoff: () => createHandoff,
9452
+ createIdp: () => createIdp,
9413
9453
  createProject: () => createProject,
9414
9454
  createRelease: () => createRelease,
9415
9455
  createSchema: () => createSchema,
9416
9456
  createSession: () => createSession,
9417
9457
  createTeam: () => createTeam,
9418
9458
  createUser: () => createUser,
9419
- deleteFlowDefinition: () => deleteFlowDefinition,
9420
9459
  deleteGrant: () => deleteGrant,
9421
9460
  deleteTeam: () => deleteTeam,
9422
9461
  deleteUserByID: () => deleteUserByID,
9462
+ deleteVariable: () => deleteVariable,
9423
9463
  exchangeHandoff: () => exchangeHandoff,
9424
9464
  finishUserPasskeyRegistration: () => finishUserPasskeyRegistration,
9425
9465
  getAuthAttempt: () => getAuthAttempt,
9426
9466
  getBeginUserPasskeyRegistrationUrl: () => getBeginUserPasskeyRegistrationUrl,
9427
9467
  getBrandingById: () => getBrandingById,
9428
9468
  getClaimStatus: () => getClaimStatus,
9469
+ getClaimWindow: () => getClaimWindow,
9429
9470
  getCompleteClaimUrl: () => getCompleteClaimUrl,
9430
9471
  getCreateAuthAttemptUrl: () => getCreateAuthAttemptUrl,
9431
9472
  getCreateBrandingUrl: () => getCreateBrandingUrl,
@@ -9433,16 +9474,17 @@ var zitadelNextGen_exports = /* @__PURE__ */ __exportAll({
9433
9474
  getCreateFlowUrl: () => getCreateFlowUrl,
9434
9475
  getCreateGrantUrl: () => getCreateGrantUrl,
9435
9476
  getCreateHandoffUrl: () => getCreateHandoffUrl,
9477
+ getCreateIdpUrl: () => getCreateIdpUrl,
9436
9478
  getCreateProjectUrl: () => getCreateProjectUrl,
9437
9479
  getCreateReleaseUrl: () => getCreateReleaseUrl,
9438
9480
  getCreateSchemaUrl: () => getCreateSchemaUrl,
9439
9481
  getCreateSessionUrl: () => getCreateSessionUrl,
9440
9482
  getCreateTeamUrl: () => getCreateTeamUrl,
9441
9483
  getCreateUserUrl: () => getCreateUserUrl,
9442
- getDeleteFlowDefinitionUrl: () => getDeleteFlowDefinitionUrl,
9443
9484
  getDeleteGrantUrl: () => getDeleteGrantUrl,
9444
9485
  getDeleteTeamUrl: () => getDeleteTeamUrl,
9445
9486
  getDeleteUserByIDUrl: () => getDeleteUserByIDUrl,
9487
+ getDeleteVariableUrl: () => getDeleteVariableUrl,
9446
9488
  getEnvironmentByName: () => getEnvironmentByName,
9447
9489
  getEvent: () => getEvent,
9448
9490
  getExchangeHandoffUrl: () => getExchangeHandoffUrl,
@@ -9452,12 +9494,15 @@ var zitadelNextGen_exports = /* @__PURE__ */ __exportAll({
9452
9494
  getGetAuthAttemptUrl: () => getGetAuthAttemptUrl,
9453
9495
  getGetBrandingByIdUrl: () => getGetBrandingByIdUrl,
9454
9496
  getGetClaimStatusUrl: () => getGetClaimStatusUrl,
9497
+ getGetClaimWindowUrl: () => getGetClaimWindowUrl,
9455
9498
  getGetEnvironmentByNameUrl: () => getGetEnvironmentByNameUrl,
9456
9499
  getGetEventUrl: () => getGetEventUrl,
9457
9500
  getGetFlowDefinitionUrl: () => getGetFlowDefinitionUrl,
9458
9501
  getGetFlowStepUrl: () => getGetFlowStepUrl,
9459
9502
  getGetGrantUrl: () => getGetGrantUrl,
9460
9503
  getGetHealthUrl: () => getGetHealthUrl,
9504
+ getGetIdpByIdUrl: () => getGetIdpByIdUrl,
9505
+ getGetIdpRevisionByIdUrl: () => getGetIdpRevisionByIdUrl,
9461
9506
  getGetLiveUrl: () => getGetLiveUrl,
9462
9507
  getGetMySessionUrl: () => getGetMySessionUrl,
9463
9508
  getGetMyUserUrl: () => getGetMyUserUrl,
@@ -9468,14 +9513,20 @@ var zitadelNextGen_exports = /* @__PURE__ */ __exportAll({
9468
9513
  getGetSessionUrl: () => getGetSessionUrl,
9469
9514
  getGetTeamUrl: () => getGetTeamUrl,
9470
9515
  getGetUserByIDUrl: () => getGetUserByIDUrl,
9516
+ getGetVariableUrl: () => getGetVariableUrl,
9517
+ getGetVariablesUrl: () => getGetVariablesUrl,
9471
9518
  getGrant: () => getGrant,
9472
9519
  getHealth: () => getHealth,
9520
+ getIdpById: () => getIdpById,
9521
+ getIdpRevisionById: () => getIdpRevisionById,
9473
9522
  getInitClaimUrl: () => getInitClaimUrl,
9474
9523
  getIssueChallengeUrl: () => getIssueChallengeUrl,
9475
9524
  getListBrandingUrl: () => getListBrandingUrl,
9476
9525
  getListEnvironmentsUrl: () => getListEnvironmentsUrl,
9477
9526
  getListEventsUrl: () => getListEventsUrl,
9478
9527
  getListFlowDefinitionsUrl: () => getListFlowDefinitionsUrl,
9528
+ getListIdpRevisionsUrl: () => getListIdpRevisionsUrl,
9529
+ getListMyProjectsUrl: () => getListMyProjectsUrl,
9479
9530
  getListReleasesUrl: () => getListReleasesUrl,
9480
9531
  getListSchemasUrl: () => getListSchemasUrl,
9481
9532
  getListUserPasskeysUrl: () => getListUserPasskeysUrl,
@@ -9483,8 +9534,12 @@ var zitadelNextGen_exports = /* @__PURE__ */ __exportAll({
9483
9534
  getLive: () => getLive,
9484
9535
  getMySession: () => getMySession,
9485
9536
  getMyUser: () => getMyUser,
9537
+ getPatchMyUserUrl: () => getPatchMyUserUrl,
9486
9538
  getPatchProjectUrl: () => getPatchProjectUrl,
9539
+ getPatchUserByIDUrl: () => getPatchUserByIDUrl,
9487
9540
  getProject: () => getProject,
9541
+ getQueryGrantsUrl: () => getQueryGrantsUrl,
9542
+ getQueryIdpsUrl: () => getQueryIdpsUrl,
9488
9543
  getQueryProjectsUrl: () => getQueryProjectsUrl,
9489
9544
  getQuerySessionsUrl: () => getQuerySessionsUrl,
9490
9545
  getQueryTeamsUrl: () => getQueryTeamsUrl,
@@ -9498,9 +9553,11 @@ var zitadelNextGen_exports = /* @__PURE__ */ __exportAll({
9498
9553
  getSetUserPasswordUrl: () => getSetUserPasswordUrl,
9499
9554
  getSubmitFlowStepUrl: () => getSubmitFlowStepUrl,
9500
9555
  getTeam: () => getTeam,
9501
- getUpdateFlowDefinitionUrl: () => getUpdateFlowDefinitionUrl,
9502
9556
  getUpdateTeamUrl: () => getUpdateTeamUrl,
9557
+ getUpdateVariablesUrl: () => getUpdateVariablesUrl,
9503
9558
  getUserByID: () => getUserByID,
9559
+ getVariable: () => getVariable,
9560
+ getVariables: () => getVariables,
9504
9561
  getVerifyChallengeProofUrl: () => getVerifyChallengeProofUrl,
9505
9562
  initClaim: () => initClaim,
9506
9563
  issueChallenge: () => issueChallenge,
@@ -9508,11 +9565,17 @@ var zitadelNextGen_exports = /* @__PURE__ */ __exportAll({
9508
9565
  listEnvironments: () => listEnvironments,
9509
9566
  listEvents: () => listEvents,
9510
9567
  listFlowDefinitions: () => listFlowDefinitions,
9568
+ listIdpRevisions: () => listIdpRevisions,
9569
+ listMyProjects: () => listMyProjects,
9511
9570
  listReleases: () => listReleases,
9512
9571
  listSchemas: () => listSchemas,
9513
9572
  listUserPasskeys: () => listUserPasskeys,
9514
9573
  listUserTeams: () => listUserTeams,
9574
+ patchMyUser: () => patchMyUser,
9515
9575
  patchProject: () => patchProject,
9576
+ patchUserByID: () => patchUserByID,
9577
+ queryGrants: () => queryGrants,
9578
+ queryIdps: () => queryIdps,
9516
9579
  queryProjects: () => queryProjects,
9517
9580
  querySessions: () => querySessions,
9518
9581
  queryTeams: () => queryTeams,
@@ -9521,8 +9584,8 @@ var zitadelNextGen_exports = /* @__PURE__ */ __exportAll({
9521
9584
  revokeSession: () => revokeSession,
9522
9585
  setUserPassword: () => setUserPassword,
9523
9586
  submitFlowStep: () => submitFlowStep,
9524
- updateFlowDefinition: () => updateFlowDefinition,
9525
9587
  updateTeam: () => updateTeam,
9588
+ updateVariables: () => updateVariables,
9526
9589
  verifyChallengeProof: () => verifyChallengeProof
9527
9590
  });
9528
9591
  const getGetHealthUrl = () => {
@@ -9592,9 +9655,14 @@ const getQueryUsersUrl = () => {
9592
9655
  /**
9593
9656
  * Returns the users of a project, paginated with a cursor.
9594
9657
 
9595
- The project comes from the credential, not from a parameter: the operation
9596
- is bound to the token's own project by construction. This is why it takes
9597
- no `project_id`, unlike the other query endpoints.
9658
+ The project comes from the credential, not from a parameter: the
9659
+ operation is bound to the credential's home project by construction
9660
+ (oauth2 secret or user-bound session). This is why it takes no
9661
+ `project_id`, unlike the other query endpoints.
9662
+
9663
+ Accepts either a project secret (`oauth2`) or a user-bound Console
9664
+ session cookie (`nextgenSession`). CSRF/Origin for cookie mutations
9665
+ is a follow-up (#1140).
9598
9666
 
9599
9667
  * @summary Query users
9600
9668
  */
@@ -9626,6 +9694,28 @@ const getUserByID = async (userId, params, options) => {
9626
9694
  method: "GET"
9627
9695
  });
9628
9696
  };
9697
+ const getPatchUserByIDUrl = (userId) => {
9698
+ return `${getProxyPath()}/users/${userId}`;
9699
+ };
9700
+ /**
9701
+ * Partially updates a user's schema-defined attributes, and optionally
9702
+ moves the user to another registered schema. The merged result is
9703
+ validated against the schema before commit. Concurrent writes are
9704
+ last-write-wins.
9705
+
9706
+ * @summary Update user by ID
9707
+ */
9708
+ const patchUserByID = async (userId, patchUserByIDBody, options) => {
9709
+ return customFetch(getPatchUserByIDUrl(userId), {
9710
+ ...options,
9711
+ method: "PATCH",
9712
+ headers: {
9713
+ "Content-Type": "application/json",
9714
+ ...options?.headers
9715
+ },
9716
+ body: JSON.stringify(patchUserByIDBody)
9717
+ });
9718
+ };
9629
9719
  const getDeleteUserByIDUrl = (userId) => {
9630
9720
  return `${getProxyPath()}/users/${userId}`;
9631
9721
  };
@@ -9756,6 +9846,52 @@ const getMyUser = async (options) => {
9756
9846
  method: "GET"
9757
9847
  });
9758
9848
  };
9849
+ const getPatchMyUserUrl = () => {
9850
+ return `${getProxyPath()}/users/me`;
9851
+ };
9852
+ /**
9853
+ * Partially updates the caller's own schema-defined attributes. The merged
9854
+ result is validated against the user's schema before commit. Concurrent
9855
+ writes are last-write-wins.
9856
+
9857
+ * @summary Update my user information
9858
+ */
9859
+ const patchMyUser = async (patchMyUserBody, options) => {
9860
+ return customFetch(getPatchMyUserUrl(), {
9861
+ ...options,
9862
+ method: "PATCH",
9863
+ headers: {
9864
+ "Content-Type": "application/json",
9865
+ ...options?.headers
9866
+ },
9867
+ body: JSON.stringify(patchMyUserBody)
9868
+ });
9869
+ };
9870
+ const getListMyProjectsUrl = (params) => {
9871
+ const normalizedParams = new URLSearchParams();
9872
+ Object.entries(params || {}).forEach(([key, value]) => {
9873
+ if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
9874
+ });
9875
+ const stringifiedParams = normalizedParams.toString();
9876
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/users/me/projects?${stringifiedParams}` : `${getProxyPath()}/users/me/projects`;
9877
+ };
9878
+ /**
9879
+ * The projects the signed-in user holds an active grant on, either directly or
9880
+ through a team they are a member of. Ordered by project id and paged with an
9881
+ opaque cursor.
9882
+
9883
+ This is not the same question as `queryProjects`, which answers with the one
9884
+ project the calling credential is bound to. Here the session is the caller
9885
+ and the grants are the answer, so the list spans projects.
9886
+
9887
+ * @summary List the projects I can act on
9888
+ */
9889
+ const listMyProjects = async (params, options) => {
9890
+ return customFetch(getListMyProjectsUrl(params), {
9891
+ ...options,
9892
+ method: "GET"
9893
+ });
9894
+ };
9759
9895
  const getCreateFlowUrl = () => {
9760
9896
  return `${getProxyPath()}/flow`;
9761
9897
  };
@@ -10062,7 +10198,11 @@ const getGetClaimStatusUrl = (projectId, params) => {
10062
10198
  * Polled by the CLI while a browser completes the claim. Authorized by the
10063
10199
  project secret that initiated the challenge. Returns `pending`, or
10064
10200
  `completed` with the owning team, the claim timestamp, and the dashboard
10065
- URL once the browser leg has finished.
10201
+ URL once the project is claimed. The claim grant, not the polled
10202
+ challenge, is the source of truth: a project claimed through another
10203
+ concurrent challenge also reports `completed`, and a completed claim
10204
+ keeps reporting `completed` past this challenge's expiry and past the
10205
+ project's claim window.
10066
10206
 
10067
10207
  * @summary Get claim status
10068
10208
  */
@@ -10094,6 +10234,30 @@ const completeClaim = async (projectId, completeClaimBody, options) => {
10094
10234
  body: JSON.stringify(completeClaimBody)
10095
10235
  });
10096
10236
  };
10237
+ const getGetClaimWindowUrl = (projectId, params) => {
10238
+ const normalizedParams = new URLSearchParams();
10239
+ Object.entries(params || {}).forEach(([key, value]) => {
10240
+ if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
10241
+ });
10242
+ const stringifiedParams = normalizedParams.toString();
10243
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/projects/${projectId}/claim/window?${stringifiedParams}` : `${getProxyPath()}/projects/${projectId}/claim/window`;
10244
+ };
10245
+ /**
10246
+ * Read by the claim page in the browser to show how long is left to claim the
10247
+ project. Unauthenticated by design: the claim page runs this before the
10248
+ developer has signed in, and holding the `challenge_id` from the claim URL
10249
+ is the authorization — the same capability `claim/complete` accepts. It
10250
+ reveals only the window, never the project itself, and unlike
10251
+ `claim/complete` it spends nothing, so a reload is free.
10252
+
10253
+ * @summary Get the claim window for a project
10254
+ */
10255
+ const getClaimWindow = async (projectId, params, options) => {
10256
+ return customFetch(getGetClaimWindowUrl(projectId, params), {
10257
+ ...options,
10258
+ method: "GET"
10259
+ });
10260
+ };
10097
10261
  const getCreateSessionUrl = () => {
10098
10262
  return `${getProxyPath()}/sessions`;
10099
10263
  };
@@ -10355,12 +10519,17 @@ const getCreateFlowDefinitionUrl = () => {
10355
10519
  return `${getProxyPath()}/flow_definitions`;
10356
10520
  };
10357
10521
  /**
10358
- * Creates a new flow definition.
10522
+ * Publishes a new flow definition revision.
10359
10523
  Flow definitions are templates that define the sequence of steps (capabilities)
10360
10524
  for a particular user journey (e.g., registration, login, password reset).
10361
10525
 
10362
10526
  Flow definitions are created based on the flow definition schema, which includes the flow's purpose, audience, and the steps involved.
10363
10527
 
10528
+ Every call allocates a new opaque id. Revisions of one flow share its
10529
+ `name`; posting a definition under an existing `name` publishes a new
10530
+ revision of that flow, it is not a conflict. List with `name` to see a
10531
+ flow's revisions, newest by creation time first.
10532
+
10364
10533
  * @summary Create a new flow definition
10365
10534
  */
10366
10535
  const createFlowDefinition = async (createFlowDefinitionBody, options) => {
@@ -10383,8 +10552,9 @@ const getListFlowDefinitionsUrl = (params) => {
10383
10552
  return stringifiedParams.length > 0 ? `${getProxyPath()}/flow_definitions?${stringifiedParams}` : `${getProxyPath()}/flow_definitions`;
10384
10553
  };
10385
10554
  /**
10386
- * Retrieves a list of all flow definitions.
10555
+ * Retrieves a list of all flow definitions, newest by creation time first.
10387
10556
  This endpoint can be used to view existing flow definitions and their configurations.
10557
+ Filter by `name` to list the revisions of one flow.
10388
10558
 
10389
10559
  * @summary List flow definitions
10390
10560
  */
@@ -10407,42 +10577,6 @@ const getFlowDefinition = async (id, options) => {
10407
10577
  method: "GET"
10408
10578
  });
10409
10579
  };
10410
- const getUpdateFlowDefinitionUrl = (id) => {
10411
- return `${getProxyPath()}/flow_definitions/${id}`;
10412
- };
10413
- /**
10414
- * Update a flow definition by id. This endpoint replaces the existing flow definition.
10415
- If `flow_definition.status` is omitted, the current status is preserved
10416
-
10417
- * @summary Update a flow definition by id
10418
- */
10419
- const updateFlowDefinition = async (id, updateFlowDefinitionBody, options) => {
10420
- return customFetch(getUpdateFlowDefinitionUrl(id), {
10421
- ...options,
10422
- method: "PUT",
10423
- headers: {
10424
- "Content-Type": "application/json",
10425
- ...options?.headers
10426
- },
10427
- body: JSON.stringify(updateFlowDefinitionBody)
10428
- });
10429
- };
10430
- const getDeleteFlowDefinitionUrl = (id) => {
10431
- return `${getProxyPath()}/flow_definitions/${id}`;
10432
- };
10433
- /**
10434
- * Delete a flow definition by id.
10435
- If the flow definition is currently being used by a flow, the deletion will fail.
10436
- If the flow definition is the last active flow definition for a given purpose, the deletion will fail to prevent disruption of new flows being started for that purpose.
10437
-
10438
- * @summary Delete a flow definition by id
10439
- */
10440
- const deleteFlowDefinition = async (id, options) => {
10441
- return customFetch(getDeleteFlowDefinitionUrl(id), {
10442
- ...options,
10443
- method: "DELETE"
10444
- });
10445
- };
10446
10580
  const getCreateTeamUrl = (params) => {
10447
10581
  const normalizedParams = new URLSearchParams();
10448
10582
  Object.entries(params || {}).forEach(([key, value]) => {
@@ -10516,6 +10650,11 @@ users whose lifecycle it owns are deactivated with it.
10516
10650
  The request is idempotent. Deleting a team that is already deactivated
10517
10651
  or doesn't exist succeeds without changing anything.
10518
10652
 
10653
+ A team that still owns a project is refused with `409
10654
+ team.owns_project`: deactivating it would leave the project owned by a
10655
+ dead team and so unmanageable. No endpoint releases that ownership yet,
10656
+ so such a team cannot currently be deactivated through the API.
10657
+
10519
10658
  * @summary Delete team
10520
10659
  */
10521
10660
  const deleteTeam = async (teamId, options) => {
@@ -10595,8 +10734,7 @@ const getCreateBrandingUrl = (params) => {
10595
10734
  /**
10596
10735
  * Publishes a new immutable branding revision for the project. Branding
10597
10736
  revisions cannot be updated or deleted; every edit publishes a new
10598
- revision, and flow responses resolve the latest revision per project
10599
- (see ADR 040).
10737
+ revision, and flow responses resolve the latest revision per project.
10600
10738
 
10601
10739
  The `liquid_template` is validated lexically on save (size, encoding,
10602
10740
  banned patterns such as `<script>` tags, inline event handlers, and the
@@ -10627,9 +10765,9 @@ const getListBrandingUrl = (params) => {
10627
10765
  /**
10628
10766
  * Lists branding revisions for the project, newest first, capped at the
10629
10767
  100 most recent. The first entry is the revision flow responses
10630
- currently resolve. Deliberately unpaginated in v1 — list endpoints
10631
- gain a real query mechanism together (ADR 031); advertising pagination
10632
- parameters the server ignores would be worse than none.
10768
+ currently resolve. Deliberately unpaginated in v1 — list endpoints gain a
10769
+ real query mechanism together; advertising pagination parameters the
10770
+ server ignores would be worse than none.
10633
10771
 
10634
10772
  * @summary List branding revisions
10635
10773
  */
@@ -10652,6 +10790,142 @@ const getBrandingById = async (id, options) => {
10652
10790
  method: "GET"
10653
10791
  });
10654
10792
  };
10793
+ const getCreateIdpUrl = (params) => {
10794
+ const normalizedParams = new URLSearchParams();
10795
+ Object.entries(params || {}).forEach(([key, value]) => {
10796
+ if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
10797
+ });
10798
+ const stringifiedParams = normalizedParams.toString();
10799
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/idps?${stringifiedParams}` : `${getProxyPath()}/idps`;
10800
+ };
10801
+ /**
10802
+ * Creates or revises a connection document. If the slug does not already
10803
+ exist, a new connection is created. If a connection with that slug already
10804
+ exists, a revision is created.
10805
+
10806
+ A revision gets a new `revision_id` and does not modify the connection
10807
+ `id`, so identity links that reference the connection keep resolving while
10808
+ releases and auth attempts stay pinned to the revision they captured.
10809
+
10810
+ Identity fields are fixed for the life of the connection and a revision
10811
+ that changes one is rejected: `protocol`, `subject_claim`, and the
10812
+ endpoints that name the authority (`issuer` for OIDC, `token_endpoint`
10813
+ and `userinfo_endpoint` for OAuth 2.0). Their values decide which provider
10814
+ account a stored subject belongs to, so changing one would silently
10815
+ repoint existing identities at a different provider.
10816
+
10817
+ `subject_claim` is optional for OIDC and required for OAuth 2.0. For an
10818
+ OIDC connection, `sub` is used as the default value. If a revision updates
10819
+ `subject_claim` to a different value than the one set during creation, the
10820
+ request is rejected.
10821
+
10822
+ The document is validated against the `idp-connection.json` schema before
10823
+ anything is stored.
10824
+
10825
+ * @summary Create or revise an identity provider connection
10826
+ */
10827
+ const createIdp = async (createIdpBody, params, options) => {
10828
+ return customFetch(getCreateIdpUrl(params), {
10829
+ ...options,
10830
+ method: "POST",
10831
+ headers: {
10832
+ "Content-Type": "application/json",
10833
+ ...options?.headers
10834
+ },
10835
+ body: JSON.stringify(createIdpBody)
10836
+ });
10837
+ };
10838
+ const getQueryIdpsUrl = (params) => {
10839
+ const normalizedParams = new URLSearchParams();
10840
+ Object.entries(params || {}).forEach(([key, value]) => {
10841
+ if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
10842
+ });
10843
+ const stringifiedParams = normalizedParams.toString();
10844
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/idps/query?${stringifiedParams}` : `${getProxyPath()}/idps/query`;
10845
+ };
10846
+ /**
10847
+ * Returns the identity provider connections of a project, paginated with a
10848
+ cursor. One row per connection, carrying its newest revision.
10849
+
10850
+ * @summary Query identity provider connections
10851
+ */
10852
+ const queryIdps = async (queryIdpsBody, params, options) => {
10853
+ return customFetch(getQueryIdpsUrl(params), {
10854
+ ...options,
10855
+ method: "POST",
10856
+ headers: {
10857
+ "Content-Type": "application/json",
10858
+ ...options?.headers
10859
+ },
10860
+ body: JSON.stringify(queryIdpsBody)
10861
+ });
10862
+ };
10863
+ const getGetIdpByIdUrl = (id, params) => {
10864
+ const normalizedParams = new URLSearchParams();
10865
+ Object.entries(params || {}).forEach(([key, value]) => {
10866
+ if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
10867
+ });
10868
+ const stringifiedParams = normalizedParams.toString();
10869
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/idps/${id}?${stringifiedParams}` : `${getProxyPath()}/idps/${id}`;
10870
+ };
10871
+ /**
10872
+ * Reads a connection by its id, at its newest revision.
10873
+ `GET /idps/{id}/revisions` lists every revision of the connection.
10874
+
10875
+ The lookup is scoped to the project in `project_id`.
10876
+
10877
+ * @summary Get an identity provider connection by id
10878
+ */
10879
+ const getIdpById = async (id, params, options) => {
10880
+ return customFetch(getGetIdpByIdUrl(id, params), {
10881
+ ...options,
10882
+ method: "GET"
10883
+ });
10884
+ };
10885
+ const getListIdpRevisionsUrl = (id, params) => {
10886
+ const normalizedParams = new URLSearchParams();
10887
+ Object.entries(params || {}).forEach(([key, value]) => {
10888
+ if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
10889
+ });
10890
+ const stringifiedParams = normalizedParams.toString();
10891
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/idps/${id}/revisions?${stringifiedParams}` : `${getProxyPath()}/idps/${id}/revisions`;
10892
+ };
10893
+ /**
10894
+ * Returns every revision of one connection, newest first, paginated with a
10895
+ cursor. The order is fixed, so there is no `sorting`.
10896
+
10897
+ The lookup is scoped to the project in `project_id`.
10898
+
10899
+ * @summary List the revisions of an identity provider connection
10900
+ */
10901
+ const listIdpRevisions = async (id, params, options) => {
10902
+ return customFetch(getListIdpRevisionsUrl(id, params), {
10903
+ ...options,
10904
+ method: "GET"
10905
+ });
10906
+ };
10907
+ const getGetIdpRevisionByIdUrl = (revisionId, params) => {
10908
+ const normalizedParams = new URLSearchParams();
10909
+ Object.entries(params || {}).forEach(([key, value]) => {
10910
+ if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
10911
+ });
10912
+ const stringifiedParams = normalizedParams.toString();
10913
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/idps/revisions/${revisionId}?${stringifiedParams}` : `${getProxyPath()}/idps/revisions/${revisionId}`;
10914
+ };
10915
+ /**
10916
+ * Reads one revision of a connection by its `revision_id`, the value an auth
10917
+ attempt or a release pins.
10918
+
10919
+ The lookup is scoped to the project in `project_id`.
10920
+
10921
+ * @summary Get one revision of an identity provider connection
10922
+ */
10923
+ const getIdpRevisionById = async (revisionId, params, options) => {
10924
+ return customFetch(getGetIdpRevisionByIdUrl(revisionId, params), {
10925
+ ...options,
10926
+ method: "GET"
10927
+ });
10928
+ };
10655
10929
  const getCreateReleaseUrl = (params) => {
10656
10930
  const normalizedParams = new URLSearchParams();
10657
10931
  Object.entries(params || {}).forEach(([key, value]) => {
@@ -10751,10 +11025,23 @@ const getCreateGrantUrl = (params) => {
10751
11025
  /**
10752
11026
  * Bind a user or team to `project.viewer`, `project.editor`, or
10753
11027
  `project.admin` on the project identified by the `project-id` header.
10754
- IDs are `asgn_<opaque>`. Owning-team (`project.team`) grants are not
10755
- created here — claim owns that path. An unrevoked grant with the same
10756
- principal and relation occupies the unique key even after `expires_at`;
10757
- DELETE it before re-creating.
11028
+ Name the principal with `user` (`user_id` or `identifier`) or `team`
11029
+ (`team_id` or `name`). IDs are `asgn_<opaque>`. Owning-team
11030
+ (`project.team`) grants are not created here — claim owns that path. An
11031
+ unrevoked grant with the same principal and relation occupies the unique
11032
+ key even after `expires_at`; DELETE it before re-creating.
11033
+ Create does not accept `expand`. The `user_id` and team locators
11034
+ return 201, whose `user` / `team` carry only `user_id` / `team_id`,
11035
+ and still 404 / 409 when the principal is missing or the tuple
11036
+ already exists. Creating by `user.identifier` answers 202 with no
11037
+ body on every outcome. Granting the session caller's own user is
11038
+ `grant.invalid` on either user locator. Read the grant back for
11039
+ identifier and display.
11040
+
11041
+ Accepts either a project secret (`oauth2`) or a user-bound Console
11042
+ session cookie (`nextgenSession`). Session callers are authorized as
11043
+ the human against the target project (home may differ). CSRF/Origin
11044
+ for cookie mutations is a follow-up (#1140).
10758
11045
 
10759
11046
  * @summary Create grant
10760
11047
  */
@@ -10769,6 +11056,45 @@ const createGrant = async (createGrantBody, params, options) => {
10769
11056
  body: JSON.stringify(createGrantBody)
10770
11057
  });
10771
11058
  };
11059
+ const getQueryGrantsUrl = (params) => {
11060
+ const normalizedParams = new URLSearchParams();
11061
+ Object.entries(params || {}).forEach(([key, value]) => {
11062
+ if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
11063
+ });
11064
+ const stringifiedParams = normalizedParams.toString();
11065
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/grants/query?${stringifiedParams}` : `${getProxyPath()}/grants/query`;
11066
+ };
11067
+ /**
11068
+ * Returns the collaboration grants of a project, paginated with a cursor.
11069
+ Only unrevoked grants this API manages are listed (user or team bound to
11070
+ viewer, editor, or admin), including expired grants so a client can
11071
+ DELETE before re-granting. Project-secret setup (`sk_proj`) and
11072
+ owning-team (`relation=team`) rows are not returned. Grants are not in
11073
+ `resource_scope_index`; project scope is required on the query (same as
11074
+ get). Requires `project.read`. `expand: ["principal"]` adds envelope
11075
+ fields on `user` / `team` and additionally requires `user.read` and
11076
+ `team.read` for project secrets (documented on the expand enum; those
11077
+ scopes cannot be ANDed onto this security block because they are
11078
+ body-conditional). A user-bound Console session that already passed
11079
+ the project Check may expand without those scopes.
11080
+
11081
+ Accepts either a project secret (`oauth2`) or a user-bound Console
11082
+ session cookie (`nextgenSession`). CSRF/Origin for cookie mutations
11083
+ is a follow-up (#1140).
11084
+
11085
+ * @summary Query grants
11086
+ */
11087
+ const queryGrants = async (queryGrantsBody, params, options) => {
11088
+ return customFetch(getQueryGrantsUrl(params), {
11089
+ ...options,
11090
+ method: "POST",
11091
+ headers: {
11092
+ "Content-Type": "application/json",
11093
+ ...options?.headers
11094
+ },
11095
+ body: JSON.stringify(queryGrantsBody)
11096
+ });
11097
+ };
10772
11098
  const getGetGrantUrl = (id, params) => {
10773
11099
  const normalizedParams = new URLSearchParams();
10774
11100
  Object.entries(params || {}).forEach(([key, value]) => {
@@ -10786,6 +11112,14 @@ still ignores expired grants. Grants are not registered in
10786
11112
  `resource_scope_index`; project scope is required on the query (same as
10787
11113
  events). Misses, revoked rows, project-secret setup (`sk_proj`),
10788
11114
  owning-team (`relation=team`) rows, and cross-project ids return 404.
11115
+ `expand=principal` adds envelope fields on `user` or `team` and requires
11116
+ `user.read` and `team.read` in addition to `project.read` for project
11117
+ secrets. A user-bound Console session that already passed the project
11118
+ Check may expand without those scopes.
11119
+
11120
+ Accepts either a project secret (`oauth2`) or a user-bound Console
11121
+ session cookie (`nextgenSession`). CSRF/Origin for cookie mutations
11122
+ is a follow-up (#1140).
10789
11123
 
10790
11124
  * @summary Get grant
10791
11125
  */
@@ -10809,6 +11143,10 @@ Already-revoked, missing, project-secret setup, and owning-team grants
10809
11143
  return 404. The row is not un-revoked. Expired grants can still be
10810
11144
  revoked so the unique binding can be reused.
10811
11145
 
11146
+ Accepts either a project secret (`oauth2`) or a user-bound Console
11147
+ session cookie (`nextgenSession`). CSRF/Origin for cookie mutations
11148
+ is a follow-up (#1140).
11149
+
10812
11150
  * @summary Revoke grant
10813
11151
  */
10814
11152
  const deleteGrant = async (id, params, options) => {
@@ -10875,6 +11213,122 @@ const getEvent = async (id, params, options) => {
10875
11213
  method: "GET"
10876
11214
  });
10877
11215
  };
11216
+ const getGetVariablesUrl = (params) => {
11217
+ const normalizedParams = new URLSearchParams();
11218
+ Object.entries(params || {}).forEach(([key, value]) => {
11219
+ if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
11220
+ });
11221
+ const stringifiedParams = normalizedParams.toString();
11222
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/variables?${stringifiedParams}` : `${getProxyPath()}/variables`;
11223
+ };
11224
+ /**
11225
+ * Returns the variables entered at the owner this request addresses, keyed by
11226
+ name — one environment of the project with `environment_name`, the project
11227
+ level itself without it.
11228
+
11229
+ Owners are separate, not a ladder: an environment does not inherit the
11230
+ project's variables and the project does not see its environments'. Reading
11231
+ everything a project holds therefore means reading each owner in turn.
11232
+
11233
+ Secret values are not returned. A secret appears as `{"secret": true}`,
11234
+ which says a value is held without disclosing it.
11235
+
11236
+ * @summary Get variables
11237
+ */
11238
+ const getVariables = async (params, options) => {
11239
+ return customFetch(getGetVariablesUrl(params), {
11240
+ ...options,
11241
+ method: "GET"
11242
+ });
11243
+ };
11244
+ const getUpdateVariablesUrl = (params) => {
11245
+ const normalizedParams = new URLSearchParams();
11246
+ Object.entries(params || {}).forEach(([key, value]) => {
11247
+ if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
11248
+ });
11249
+ const stringifiedParams = normalizedParams.toString();
11250
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/variables?${stringifiedParams}` : `${getProxyPath()}/variables`;
11251
+ };
11252
+ /**
11253
+ * Enters, replaces and removes variables at the owner this request
11254
+ addresses.
11255
+
11256
+ Every name in the body is applied at exactly that owner — the project, or
11257
+ the environment named by `environment_name` — and reaches no other. Names
11258
+ not in the body are untouched.
11259
+
11260
+ A bare scalar enters a non-secret value. `{"value": …, "secret": true}`
11261
+ stores the value encrypted under the project's active `secret` key, after
11262
+ which it can be referenced but not read back. `null` removes the name from
11263
+ this owner (RFC 7386), which is how several variables are removed in one
11264
+ request; removing a name this owner does not hold is a no-op rather than an
11265
+ error.
11266
+
11267
+ The body is applied whole or not at all, so a rejected request leaves the
11268
+ owner exactly as it was. Writing the same name and owner twice replaces the
11269
+ value rather than duplicating it, which makes a retry safe.
11270
+
11271
+ * @summary Update variables
11272
+ */
11273
+ const updateVariables = async (updateVariablesBody, params, options) => {
11274
+ return customFetch(getUpdateVariablesUrl(params), {
11275
+ ...options,
11276
+ method: "PATCH",
11277
+ headers: {
11278
+ "Content-Type": "application/json",
11279
+ ...options?.headers
11280
+ },
11281
+ body: JSON.stringify(updateVariablesBody)
11282
+ });
11283
+ };
11284
+ const getGetVariableUrl = (variableName, params) => {
11285
+ const normalizedParams = new URLSearchParams();
11286
+ Object.entries(params || {}).forEach(([key, value]) => {
11287
+ if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
11288
+ });
11289
+ const stringifiedParams = normalizedParams.toString();
11290
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/variables/${variableName}?${stringifiedParams}` : `${getProxyPath()}/variables/${variableName}`;
11291
+ };
11292
+ /**
11293
+ * Reads one variable by name from the owner this request addresses — one
11294
+ environment of the project with `environment_name`, the project level
11295
+ itself without it.
11296
+
11297
+ A name that owner has not entered answers `var.not_found`, even when
11298
+ another owner of the same project holds it: nothing is inherited. A secret
11299
+ is found but not disclosed: the response is `{"secret": true}`.
11300
+
11301
+ * @summary Get a variable
11302
+ */
11303
+ const getVariable = async (variableName, params, options) => {
11304
+ return customFetch(getGetVariableUrl(variableName, params), {
11305
+ ...options,
11306
+ method: "GET"
11307
+ });
11308
+ };
11309
+ const getDeleteVariableUrl = (variableName, params) => {
11310
+ const normalizedParams = new URLSearchParams();
11311
+ Object.entries(params || {}).forEach(([key, value]) => {
11312
+ if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
11313
+ });
11314
+ const stringifiedParams = normalizedParams.toString();
11315
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/variables/${variableName}?${stringifiedParams}` : `${getProxyPath()}/variables/${variableName}`;
11316
+ };
11317
+ /**
11318
+ * Removes the variable this owner entered under this name.
11319
+
11320
+ A variable is deletable only by the owner that entered it. Deleting a name
11321
+ another owner of the same project holds answers `var.not_found` and leaves
11322
+ that owner's value standing.
11323
+
11324
+ * @summary Delete a variable
11325
+ */
11326
+ const deleteVariable = async (variableName, params, options) => {
11327
+ return customFetch(getDeleteVariableUrl(variableName, params), {
11328
+ ...options,
11329
+ method: "DELETE"
11330
+ });
11331
+ };
10878
11332
  //#endregion
10879
11333
  //#region ../api/dist/runtime/api-factory.mjs
10880
11334
  /**
@@ -13418,9 +13872,10 @@ function createSanitiser() {
13418
13872
  * the `Branding` JSON into a CSSStyleSheet of `:host { --zl-* }` declarations
13419
13873
  * and applies it via `shadowRoot.adoptedStyleSheets`.
13420
13874
  *
13421
- * Dark-mode overrides are emitted as `:host([data-theme="dark"]) { ... }`
13422
- * (matching `docs/design/branding/tokens.md`). Resolution between
13423
- * `light | dark | auto` happens in `<zitadel-login>`.
13875
+ * Light and dark are independent surfaces: each side's palette is emitted under
13876
+ * its own `data-theme` selector only, so a key the revision left unset on one
13877
+ * side takes the maintained default for that side rather than the other side's
13878
+ * colour. Resolution between `light | dark | auto` happens in `<zitadel-login>`.
13424
13879
  *
13425
13880
  * Base layer: the design-tokens package ships the full `--zl-*` set as both a
13426
13881
  * `.css` file (for host pages) and a `tokensCss` string (for shadow roots and
@@ -13428,13 +13883,32 @@ function createSanitiser() {
13428
13883
  * paint correctly even when the host page didn't `@import` tokens.css — the
13429
13884
  * orchestrator is meant to drop into any page.
13430
13885
  */
13431
- const RADIUS_MAP = {
13432
- none: "0",
13433
- sm: "0.25rem",
13434
- md: "0.5rem",
13435
- lg: "0.75rem",
13436
- full: "9999px"
13437
- };
13886
+ const RADIUS_STEPS = [
13887
+ "xs",
13888
+ "sm",
13889
+ "md",
13890
+ "lg",
13891
+ "xl"
13892
+ ];
13893
+ const RADIUS_RATIOS = (() => {
13894
+ const base = remToNumber(tokens.radius.md);
13895
+ const ratios = {};
13896
+ for (const step of RADIUS_STEPS) ratios[step] = remToNumber(tokens.radius[step]) / base;
13897
+ return ratios;
13898
+ })();
13899
+ const RADIUS_PRESETS = {
13900
+ none: 0,
13901
+ sm: .25,
13902
+ md: .5,
13903
+ lg: .75
13904
+ };
13905
+ const TEXT_STEPS = [
13906
+ "xs",
13907
+ "sm",
13908
+ "base",
13909
+ "lg",
13910
+ "xl"
13911
+ ];
13438
13912
  const DENSITY_MAP = {
13439
13913
  compact: {
13440
13914
  "--zl-spacing-4": "0.75rem",
@@ -13486,34 +13960,35 @@ const PALETTE_MAP = {
13486
13960
  * the moment before a theme resolves.
13487
13961
  */
13488
13962
  const BRANDING_SELECTOR = ":host, :host([data-theme=\"light\"]), :host([data-theme=\"dark\"])";
13489
- /** Dark-only overrides. Emitted after {@link BRANDING_SELECTOR}, so it wins on order. */
13963
+ /** Per-side palettes. Emitted after {@link BRANDING_SELECTOR}, so they win on order. */
13964
+ const LIGHT_SELECTOR = ":host([data-theme=\"light\"])";
13490
13965
  const DARK_SELECTOR = ":host([data-theme=\"dark\"])";
13491
13966
  /**
13492
13967
  * Build a CSS string of `:host { --zl-* }` declarations from a Branding
13493
- * payload. Light values land on `:host`; dark overrides land on
13494
- * `:host([data-theme="dark"])`.
13495
- */
13496
- function buildBrandingStylesheet(branding, options = {}) {
13497
- const lightDecls = collectDeclarations(branding);
13498
- const darkPalette = branding?.theme?.dark?.palette;
13499
- const darkDecls = darkPalette ? mapPalette(darkPalette) : {};
13968
+ * payload. Shape and typography are shared across the sides and land on the
13969
+ * shared selector; each side's palette lands only under its own `data-theme`
13970
+ * selector, and nowhere else.
13971
+ *
13972
+ * No copy of the resolved side goes on the shared selector. That copy would
13973
+ * reach `[data-theme="light"]` as well, so a key set on the dark side and not
13974
+ * the light one would paint on both, with nothing in the light block to
13975
+ * override it. Callers stamp `data-theme` on the host — `applySurfaceTheme`
13976
+ * does it in the same update that adopts this sheet — so the per-side block is
13977
+ * already matching by the time anything paints.
13978
+ */
13979
+ function buildBrandingStylesheet(branding) {
13980
+ const shared = collectDeclarations(branding);
13981
+ const light = mapPalette(branding?.theme?.light?.palette);
13982
+ const dark = mapPalette(branding?.theme?.dark?.palette);
13500
13983
  const blocks = [];
13501
- if (Object.keys(lightDecls).length > 0) blocks.push(formatBlock(BRANDING_SELECTOR, lightDecls));
13502
- if (Object.keys(darkDecls).length > 0) blocks.push(formatBlock(DARK_SELECTOR, darkDecls));
13503
- if (options.resolvedTheme === "dark" && darkPalette) {
13504
- const merged = {
13505
- ...lightDecls,
13506
- ...darkDecls
13507
- };
13508
- blocks.length = 0;
13509
- blocks.push(formatBlock(BRANDING_SELECTOR, merged));
13510
- }
13984
+ if (Object.keys(shared).length > 0) blocks.push(formatBlock(BRANDING_SELECTOR, shared));
13985
+ if (Object.keys(light).length > 0) blocks.push(formatBlock(LIGHT_SELECTOR, light));
13986
+ if (Object.keys(dark).length > 0) blocks.push(formatBlock(DARK_SELECTOR, dark));
13511
13987
  return blocks.join("\n");
13512
13988
  }
13513
13989
  function collectDeclarations(branding) {
13514
13990
  if (!branding) return {};
13515
13991
  const decls = {};
13516
- Object.assign(decls, mapPalette(branding.palette));
13517
13992
  Object.assign(decls, mapTypography(branding.typography));
13518
13993
  Object.assign(decls, mapShape(branding.shape));
13519
13994
  return decls;
@@ -13534,24 +14009,52 @@ function mapTypography(typography) {
13534
14009
  out["--zl-font-family-sans"] = typography.font_family;
13535
14010
  out["--zl-font-family-heading"] = typography.font_family;
13536
14011
  }
13537
- if (typography.font_family_heading) out["--zl-font-family-heading"] = typography.font_family_heading;
13538
- if (typography.font_family_mono) out["--zl-font-family-mono"] = typography.font_family_mono;
13539
14012
  const scale = clamp(typography.scale ?? 1, .75, 1.25);
13540
- if (scale !== 1) out["--zl-font-scale"] = `${scale}`;
14013
+ if (scale !== 1) for (const step of TEXT_STEPS) {
14014
+ out[`--zl-text-${step}-size`] = scaleRem(tokens.text[step].size, scale);
14015
+ out[`--zl-text-${step}-leading`] = scaleRem(tokens.text[step].leading, scale);
14016
+ }
13541
14017
  return out;
13542
14018
  }
13543
14019
  function mapShape(shape) {
13544
14020
  if (!shape) return {};
13545
14021
  const out = {};
13546
- if (shape.radius && RADIUS_MAP[shape.radius]) {
13547
- const radius = RADIUS_MAP[shape.radius];
13548
- out["--zl-radius-md"] = radius;
13549
- out["--zl-radius-lg"] = radius === "0" ? "0" : `calc(${radius} * 1.25)`;
13550
- out["--zl-radius-xl"] = radius === "0" ? "0" : `calc(${radius} * 1.75)`;
13551
- }
14022
+ Object.assign(out, mapRadius(shape.radius));
13552
14023
  if (shape.density) Object.assign(out, DENSITY_MAP[shape.density]);
14024
+ const logoScale = shape.logo_scale;
14025
+ if (typeof logoScale === "number" && Number.isFinite(logoScale)) out["--zl-logo-scale"] = `${clamp(logoScale, .5, 2)}`;
14026
+ return out;
14027
+ }
14028
+ /**
14029
+ * A brand picks one corner value and the whole ramp follows it in proportion,
14030
+ * which is what keeps the card rounder than the controls inside it. Both forms
14031
+ * the revision accepts land here: a preset name, or an integer number of pixels
14032
+ * for the brand that has a specific value.
14033
+ */
14034
+ function mapRadius(radius) {
14035
+ if (radius == null) return {};
14036
+ const out = {};
14037
+ if (radius === "full") {
14038
+ for (const step of RADIUS_STEPS) out[`--zl-radius-${step}`] = tokens.radius.full;
14039
+ return out;
14040
+ }
14041
+ const [base, unit] = typeof radius === "number" ? [radius, "px"] : radius in RADIUS_PRESETS ? [RADIUS_PRESETS[radius], "rem"] : [NaN, ""];
14042
+ if (Number.isNaN(base)) return {};
14043
+ for (const step of RADIUS_STEPS) {
14044
+ const value = base * RADIUS_RATIOS[step];
14045
+ out[`--zl-radius-${step}`] = value === 0 ? "0" : `${round(value)}${unit}`;
14046
+ }
13553
14047
  return out;
13554
14048
  }
14049
+ function scaleRem(value, scale) {
14050
+ return `${round(remToNumber(value) * scale)}rem`;
14051
+ }
14052
+ function remToNumber(value) {
14053
+ return Number.parseFloat(value);
14054
+ }
14055
+ function round(value) {
14056
+ return Math.round(value * 1e4) / 1e4;
14057
+ }
13555
14058
  function formatBlock(selector, decls) {
13556
14059
  return `${selector} {\n${Object.entries(decls).map(([prop, value]) => ` ${prop}: ${value};`).join("\n")}\n}`;
13557
14060
  }
@@ -13591,8 +14094,8 @@ function applyBaseTokens(shadowRoot) {
13591
14094
  * Callers should run `applyBaseTokens(shadowRoot)` first (once per shadow
13592
14095
  * root) so the base values exist before branding patches them.
13593
14096
  */
13594
- function applyBrandingTokens(shadowRoot, branding, resolvedTheme) {
13595
- const css = buildBrandingStylesheet(branding, { resolvedTheme });
14097
+ function applyBrandingTokens(shadowRoot, branding) {
14098
+ const css = buildBrandingStylesheet(branding);
13596
14099
  if (typeof CSSStyleSheet === "undefined") return;
13597
14100
  const sheet = new CSSStyleSheet();
13598
14101
  sheet.replaceSync(css);
@@ -13607,9 +14110,11 @@ function applyBrandingTokens(shadowRoot, branding, resolvedTheme) {
13607
14110
  * and a variant-derived fallback on top of the same branding input.
13608
14111
  *
13609
14112
  * Defaults to dark: the design system's primary surface, and the mode a
13610
- * hosted login page renders when a tenant states no preference.
14113
+ * hosted login page renders when a revision states no preference.
13611
14114
  */
13612
14115
  function resolveTheme(branding) {
14116
+ const [only, second] = publishedSides(branding);
14117
+ if (only && !second) return only;
13613
14118
  const mode = branding?.theme?.mode ?? "dark";
13614
14119
  if (mode === "light") return "light";
13615
14120
  if (mode === "dark") return "dark";
@@ -13627,7 +14132,7 @@ function resolveTheme(branding) {
13627
14132
  * (Arimo, named first in `--zl-font-family-sans`). The component ships this
13628
14133
  * so the auth screens render in the brand font out of the box, even when the
13629
14134
  * server returns no branding. This is the "default users can override" layer.
13630
- * - The **tenant override** (`applyFontUrl`) — `branding.font_url`, resolved
14135
+ * - The **tenant override** (`applyFontUrl`) — `branding.typography.font_url`, resolved
13631
14136
  * server-side from the app → team → project branding hierarchy.
13632
14137
  *
13633
14138
  * Both must live at document level, not inside the shadow root: browsers ignore
@@ -13656,7 +14161,7 @@ const DEFAULT_LINK_ID = "zl-default-font-link";
13656
14161
  * Design-system default brand font. Arimo is the Figma body face and leads the
13657
14162
  * `--zl-font-family-sans` stack; loading it here makes the unbranded auth UI
13658
14163
  * paint in the brand font instead of the system fallback. Tenants override it
13659
- * with `branding.font_url`. Exported so a privacy- or offline-sensitive
14164
+ * with `branding.typography.font_url`. Exported so a privacy- or offline-sensitive
13660
14165
  * deployment can self-host the same face and swap this single URL.
13661
14166
  */
13662
14167
  const DEFAULT_BRAND_FONT_HREF = "https://fonts.googleapis.com/css2?family=Arimo:ital,wght@0,400;0,500;0,600;0,700;1,400&display=swap";
@@ -13676,7 +14181,7 @@ const DEFAULT_BRAND_FONT_HREF = "https://fonts.googleapis.com/css2?family=Arimo:
13676
14181
  /** Per-document, per-link-id map of each owner's desired href. */
13677
14182
  const registries = /* @__PURE__ */ new WeakMap();
13678
14183
  /**
13679
- * Inject the tenant's `branding.font_url` stylesheet. Pass `null`/`undefined`
14184
+ * Inject the tenant's `branding.typography.font_url` stylesheet. Pass `null`/`undefined`
13680
14185
  * to withdraw this surface's registration (e.g. when a flow resolves with no
13681
14186
  * tenant font); the link disappears once no surface registers a URL.
13682
14187
  */
@@ -13686,7 +14191,7 @@ function applyFontUrl(shadowRoot, fontUrl) {
13686
14191
  /**
13687
14192
  * Inject the design-system default font stylesheet. Defaults to
13688
14193
  * {@link DEFAULT_BRAND_FONT_HREF}; pass `null` to withdraw this surface's
13689
- * registration (the orchestrator does this when a tenant `font_url` takes
14194
+ * registration (the orchestrator does this when a tenant font stylesheet takes
13690
14195
  * over, to avoid a redundant request).
13691
14196
  */
13692
14197
  function applyDefaultFont(shadowRoot, href = DEFAULT_BRAND_FONT_HREF) {
@@ -13753,7 +14258,10 @@ var ThemeController = class {
13753
14258
  * element nor the branding payload names a mode. Precedence, strongest
13754
14259
  * first: element `theme` property → `branding.theme.mode` → fallback. The
13755
14260
  * element wins because the page embedding the widget knows its own surface
13756
- * better than the tenant's stored branding does.
14261
+ * better than the tenant's stored branding does — but it selects among the
14262
+ * sides the revision published, and cannot reach one it did not. A revision
14263
+ * with a single published side therefore ignores this property: an embedder
14264
+ * pinning `theme="light"` against a dark-only revision gets dark.
13757
14265
  */
13758
14266
  setModePreference(explicit, fallback) {
13759
14267
  if (this.explicitMode === explicit && this.fallbackMode === fallback) return;
@@ -13768,6 +14276,12 @@ var ThemeController = class {
13768
14276
  this.detach();
13769
14277
  }
13770
14278
  refresh() {
14279
+ const [only, second] = publishedSides(this.branding);
14280
+ if (only && !second) {
14281
+ this.detach();
14282
+ this.update(only);
14283
+ return;
14284
+ }
13771
14285
  const mode = this.explicitMode ?? this.branding?.theme?.mode ?? this.fallbackMode;
13772
14286
  if (mode === "auto") {
13773
14287
  this.attach();
@@ -13846,9 +14360,10 @@ var ZitadelSurface = class extends i$2 {
13846
14360
  const root = this.shadowRoot;
13847
14361
  if (root) {
13848
14362
  applyBaseTokens(root);
13849
- applyBrandingTokens(root, branding, this.themeController.theme);
13850
- const tenantFontUrl = branding?.font_url ?? null;
13851
- applyDefaultFont(root, this.variant === "page" && !tenantFontUrl ? void 0 : null);
14363
+ applyBrandingTokens(root, branding);
14364
+ const page = this.variant === "page";
14365
+ const tenantFontUrl = page ? branding?.typography?.font_url ?? null : null;
14366
+ applyDefaultFont(root, page && !tenantFontUrl ? void 0 : null);
13852
14367
  applyFontUrl(root, tenantFontUrl);
13853
14368
  }
13854
14369
  this.dataset.theme = this.themeController.theme;
@@ -13884,7 +14399,7 @@ const TEMPLATE_NAMES = {
13884
14399
  };
13885
14400
  //#endregion
13886
14401
  //#region src/orchestrator/templates/layout-chrome.css?inline
13887
- var layout_chrome_default = ":host {\n color: var(--zl-foreground);\n font-family: var(--zl-font-family-sans);\n background: none;\n display: block;\n}\n\n:host([variant=\"page\"]) {\n background: var(--zl-background);\n min-height: 100%;\n}\n\nimg[data-zl-asset-broken] {\n display: none;\n}\n\n.zl-mount {\n min-height: var(--zl-page-min-height, auto);\n background: inherit;\n color: inherit;\n flex-direction: column;\n display: flex;\n container-type: inline-size;\n}\n\n:host([variant=\"page\"]) .zl-mount {\n min-height: var(--zl-page-min-height, 100vh);\n}\n\n.zl-mount > zl-page-shell, .zl-mount > [data-zl-template-root] {\n flex: auto;\n}\n\n.zl-attribution {\n background: none;\n justify-content: center;\n align-items: center;\n display: flex;\n}\n\n.zl-attribution[hidden] {\n display: none;\n}\n\n.zl-trustmark {\n gap: var(--zl-spacing-2-5);\n flex-wrap: wrap;\n}\n\n.zl-trustmark__mark {\n align-items: center;\n gap: var(--zl-spacing-2);\n font-family: var(--zl-font-family-sans);\n font-size: var(--zl-text-sm-size);\n line-height: var(--zl-text-sm-leading);\n color: var(--zl-foreground);\n text-decoration: none;\n display: inline-flex;\n}\n\n.zl-trustmark__mark:hover {\n text-underline-offset: 4px;\n text-decoration: underline;\n}\n\n.zl-trustmark__mark:focus-visible {\n outline: var(--zl-focus-width) solid var(--zl-ring);\n outline-offset: var(--zl-focus-offset);\n border-radius: var(--zl-radius-sm);\n}\n\n.zl-trustmark__logotype {\n flex-shrink: 0;\n width: 65px;\n height: 16px;\n line-height: 0;\n display: block;\n}\n\n.zl-trustmark__logotype-svg {\n width: 100%;\n height: 100%;\n display: block;\n}\n\n.zl-card-logo {\n width: auto;\n max-width: min(12rem, 60%);\n max-height: var(--zl-card-logo-max-height, 2.5rem);\n object-fit: contain;\n}\n\n.zl-card-title {\n font-family: var(--zl-font-family-heading);\n font-size: var(--zl-text-xl-size);\n font-weight: var(--zl-font-weight-medium);\n color: var(--zl-card-foreground);\n text-align: left;\n margin: 0;\n line-height: 1;\n}\n\n.zl-card-subtitle {\n font-family: var(--zl-font-family-sans);\n font-size: var(--zl-text-sm-size);\n line-height: var(--zl-text-sm-leading);\n font-weight: var(--zl-font-weight-normal);\n color: var(--zl-muted-foreground);\n margin: 0;\n}\n\n.zl-field-group {\n gap: var(--zl-spacing-7);\n flex-direction: column;\n display: flex;\n}\n\n.zl-field-group:not(:has(*)) {\n display: none;\n}\n\n.zl-card-actions {\n gap: var(--zl-spacing-3);\n flex-direction: column;\n display: flex;\n}\n\n[data-suppress-header] .zl-card-title, [data-suppress-header] .zl-card-subtitle {\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n position: absolute;\n overflow: hidden;\n}\n\n.zl-card-nav {\n font-family: var(--zl-font-family-sans);\n font-size: var(--zl-text-sm-size);\n line-height: var(--zl-text-sm-leading);\n font-weight: var(--zl-font-weight-normal);\n color: var(--zl-muted-foreground);\n text-align: center;\n margin: 0;\n}\n\n.zl-card-nav__link {\n font: inherit;\n line-height: inherit;\n color: var(--zl-link);\n text-underline-offset: 4px;\n cursor: pointer;\n background: none;\n border: none;\n margin: 0;\n padding: 0;\n text-decoration: underline;\n display: inline;\n}\n\n.zl-card-nav__link:hover {\n color: var(--zl-foreground);\n}\n\n.zl-card-nav__link:focus-visible {\n outline: var(--zl-focus-width) solid var(--zl-ring);\n outline-offset: var(--zl-focus-offset);\n}\n\n.zl-card-forgot {\n text-align: right;\n margin: 0;\n}\n\n.zl-card-forgot__link {\n font-family: var(--zl-font-family-sans);\n font-size: var(--zl-text-sm-size);\n line-height: var(--zl-text-sm-leading);\n font-weight: var(--zl-font-weight-normal);\n color: var(--zl-link);\n text-underline-offset: 4px;\n cursor: pointer;\n background: none;\n border: none;\n margin: 0;\n padding: 0;\n text-decoration: none;\n display: inline;\n}\n\n.zl-card-forgot__link:hover {\n text-decoration: underline;\n}\n\n.zl-card-forgot__link:focus-visible {\n outline: var(--zl-focus-width) solid var(--zl-ring);\n outline-offset: var(--zl-focus-offset);\n}\n\n.zl-split {\n grid-template-columns: var(--zl-split-columns, minmax(0, 1fr) minmax(0, 1fr));\n align-items: var(--zl-split-align, center);\n gap: var(--zl-spacing-8);\n width: 100%;\n min-height: 100%;\n display: grid;\n}\n\n.zl-split__brand {\n justify-content: center;\n align-items: center;\n gap: var(--zl-spacing-6);\n padding: var(--zl-spacing-8) var(--zl-spacing-4);\n flex-direction: column;\n display: flex;\n}\n\n.zl-split__logo {\n max-width: min(16rem, 60%);\n max-height: var(--zl-split-logo-max-height, 16rem);\n}\n\n.zl-split__logo:has( + .zl-split__hero) {\n max-height: var(--zl-split-logo-max-height, 8rem);\n}\n\n.zl-split__logo:has( + .zl-split__hero) + .zl-split__hero {\n max-height: min(var(--zl-split-hero-max-height, 22.5rem),\n max(6rem,\n calc(100vh - 104px - var(--zl-spacing-8) - var(--zl-spacing-8) -\n 2.5rem - var(--zl-spacing-6) - var(--zl-spacing-6) -\n var(--zl-split-logo-max-height, 8rem))));\n}\n\n.zl-split__hero {\n width: 100%;\n max-height: var(--zl-split-hero-max-height, min(22.5rem, 60vh));\n border-radius: var(--zl-radius-lg);\n object-fit: cover;\n}\n\n.zl-split__placeholder {\n aspect-ratio: 4 / 5;\n border-radius: var(--zl-radius-lg);\n background: radial-gradient(120% 120% at 18% 12%,\n var(--zl-gradient-lavender-start) 0%,\n transparent 60%),\n radial-gradient(130% 130% at 85% 92%,\n var(--zl-gradient-rose-start) 0%,\n transparent 60%),\n var(--zl-card);\n width: min(18rem, 85%);\n}\n\n.zl-split__form {\n justify-content: center;\n align-items: center;\n gap: var(--zl-spacing-6);\n flex-direction: column;\n padding: 16px;\n display: flex;\n}\n\n.zl-split__compact {\n max-width: min(12rem, 60%);\n display: none;\n}\n\nimg.zl-split__compact {\n max-height: 2.5rem;\n}\n\nimg.zl-split__compact--hero {\n border-radius: var(--zl-radius-lg);\n object-fit: cover;\n width: 100%;\n max-width: 100%;\n max-height: 6rem;\n}\n\n.zl-split--right .zl-split__brand {\n order: 2;\n}\n\n.zl-split--right .zl-split__form {\n order: 1;\n}\n\nzl-page-shell:has(.zl-split) [slot=\"footer\"].zl-attribution {\n grid-template-columns: var(--zl-split-columns, minmax(0, 1fr) minmax(0, 1fr));\n gap: var(--zl-spacing-8);\n width: 100%;\n display: grid;\n}\n\nzl-page-shell:has(.zl-split) [slot=\"footer\"].zl-attribution > * {\n grid-column: 2;\n justify-self: center;\n}\n\nzl-page-shell:has(.zl-split--right) [slot=\"footer\"].zl-attribution > * {\n grid-column: 1;\n}\n\n@container (width <= 48rem) {\n .zl-split {\n grid-template-columns: minmax(0, 1fr);\n }\n\n .zl-split__brand {\n display: var(--zl-split-brand-mobile, none);\n }\n\n .zl-split__compact {\n display: block;\n }\n\n zl-page-shell:has(.zl-split) [slot=\"footer\"].zl-attribution {\n grid-template-columns: minmax(0, 1fr);\n }\n\n zl-page-shell:has(.zl-split) .zl-attribution > *, zl-page-shell:has(.zl-split--right) [slot=\"footer\"].zl-attribution > * {\n grid-column: 1;\n }\n}\n\n.zl-hero {\n text-align: left;\n width: 100%;\n max-width: 34rem;\n color: var(--zl-foreground);\n font-family: var(--zl-font-family-sans);\n flex-direction: column;\n display: flex;\n}\n\n.zl-hero__nav {\n margin-bottom: var(--zl-spacing-8);\n justify-content: space-between;\n align-items: center;\n display: flex;\n}\n\n.zl-hero__brand {\n align-items: center;\n gap: var(--zl-spacing-2);\n letter-spacing: -.01em;\n font-size: 1.125rem;\n font-weight: 700;\n display: inline-flex;\n}\n\n.zl-hero__brand-logo {\n max-height: 1.75rem;\n}\n\n.zl-hero__brand-mark {\n border-radius: var(--zl-radius-md);\n background: linear-gradient(135deg,\n var(--zl-gradient-lavender-start),\n var(--zl-gradient-rose-start));\n width: 1.75rem;\n height: 1.75rem;\n display: inline-flex;\n}\n\n.zl-hero__compact-brand {\n overflow-wrap: anywhere;\n letter-spacing: -.01em;\n max-width: 100%;\n color: var(--zl-foreground);\n margin: 0;\n font-size: 1.125rem;\n font-weight: 700;\n}\n\n.zl-hero__nav-links {\n gap: var(--zl-spacing-6);\n color: var(--zl-muted-foreground);\n font-size: .9375rem;\n display: flex;\n}\n\n.zl-hero__nav-links a {\n color: inherit;\n text-decoration: none;\n}\n\n.zl-hero__nav-links a:hover {\n text-decoration: underline;\n}\n\n.zl-hero__eyebrow {\n margin: 0 0 var(--zl-spacing-4);\n letter-spacing: .08em;\n text-transform: uppercase;\n color: var(--zl-muted-foreground);\n font-size: .8125rem;\n font-weight: 600;\n}\n\n.zl-hero__headline {\n margin: 0 0 var(--zl-spacing-4);\n font-family: var(--zl-font-family-heading);\n letter-spacing: -.02em;\n font-size: 2.75rem;\n font-weight: 700;\n line-height: 1.1;\n}\n\n.zl-hero__sub {\n margin: 0 0 var(--zl-spacing-8);\n max-width: 30rem;\n color: var(--zl-muted-foreground);\n font-size: 1.0625rem;\n line-height: 1.6;\n}\n\n.zl-hero__bullets {\n gap: var(--zl-spacing-2);\n margin: 0 0 var(--zl-spacing-8);\n flex-direction: column;\n padding: 0;\n list-style: none;\n display: flex;\n}\n\n.zl-hero__bullets li {\n align-items: center;\n gap: var(--zl-spacing-2);\n font-size: 1rem;\n display: flex;\n}\n\n.zl-hero__bullets li:before {\n content: \"✓\";\n color: var(--zl-success);\n font-weight: 700;\n}\n\n.zl-hero__footnote {\n padding-top: var(--zl-spacing-6);\n border-top: 1px solid var(--zl-border);\n color: var(--zl-muted-foreground);\n margin: 0;\n font-size: .8125rem;\n}\n\n.zl-hero__footnote strong {\n color: var(--zl-muted-foreground);\n font-weight: 600;\n}\n\n.zl-minimal {\n gap: var(--zl-spacing-6);\n flex-direction: column;\n width: min(24rem, 100%);\n margin-inline: auto;\n display: flex;\n}\n\n:host([variant=\"page\"]) .zl-minimal {\n padding: 52px 16px;\n}\n";
14402
+ var layout_chrome_default = ":host {\n color: var(--zl-foreground);\n font-family: var(--zl-font-family-sans);\n background: none;\n display: block;\n}\n\n:host([variant=\"page\"]) {\n background: var(--zl-background);\n min-height: 100%;\n}\n\nimg[data-zl-asset-broken] {\n display: none;\n}\n\n.zl-mount {\n min-height: var(--zl-page-min-height, auto);\n background: inherit;\n color: inherit;\n flex-direction: column;\n display: flex;\n container-type: inline-size;\n}\n\n:host([variant=\"page\"]) .zl-mount {\n min-height: var(--zl-page-min-height, 100vh);\n}\n\n.zl-mount > zl-page-shell, .zl-mount > [data-zl-template-root] {\n flex: auto;\n}\n\n.zl-attribution {\n background: none;\n justify-content: center;\n align-items: center;\n display: flex;\n}\n\n.zl-attribution[hidden] {\n display: none;\n}\n\n.zl-trustmark {\n gap: var(--zl-spacing-2-5);\n flex-wrap: wrap;\n}\n\n.zl-trustmark__mark {\n align-items: center;\n gap: var(--zl-spacing-2);\n font-family: var(--zl-font-family-sans);\n font-size: var(--zl-text-sm-size);\n line-height: var(--zl-text-sm-leading);\n color: var(--zl-foreground);\n text-decoration: none;\n display: inline-flex;\n}\n\n.zl-trustmark__mark:hover {\n text-underline-offset: 4px;\n text-decoration: underline;\n}\n\n.zl-trustmark__mark:focus-visible {\n outline: var(--zl-focus-width) solid var(--zl-ring);\n outline-offset: var(--zl-focus-offset);\n border-radius: var(--zl-radius-sm);\n}\n\n.zl-trustmark__logotype {\n flex-shrink: 0;\n width: 65px;\n height: 16px;\n line-height: 0;\n display: block;\n}\n\n.zl-trustmark__logotype-svg {\n width: 100%;\n height: 100%;\n display: block;\n}\n\n.zl-card-logo {\n width: auto;\n max-width: min(12rem, 60%);\n max-height: calc(var(--zl-card-logo-max-height, 2.5rem) * var(--zl-logo-scale, 1));\n object-fit: contain;\n}\n\n.zl-card-title {\n font-family: var(--zl-font-family-heading);\n font-size: var(--zl-text-xl-size);\n font-weight: var(--zl-font-weight-medium);\n color: var(--zl-card-foreground);\n text-align: left;\n margin: 0;\n line-height: 1;\n}\n\n.zl-card-subtitle {\n font-family: var(--zl-font-family-sans);\n font-size: var(--zl-text-sm-size);\n line-height: var(--zl-text-sm-leading);\n font-weight: var(--zl-font-weight-normal);\n color: var(--zl-muted-foreground);\n margin: 0;\n}\n\n.zl-field-group {\n gap: var(--zl-spacing-7);\n flex-direction: column;\n display: flex;\n}\n\n.zl-field-group:not(:has(*)) {\n display: none;\n}\n\n.zl-card-actions {\n gap: var(--zl-spacing-3);\n flex-direction: column;\n display: flex;\n}\n\n[data-suppress-header] .zl-card-title, [data-suppress-header] .zl-card-subtitle {\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n position: absolute;\n overflow: hidden;\n}\n\n.zl-card-nav {\n font-family: var(--zl-font-family-sans);\n font-size: var(--zl-text-sm-size);\n line-height: var(--zl-text-sm-leading);\n font-weight: var(--zl-font-weight-normal);\n color: var(--zl-muted-foreground);\n text-align: center;\n margin: 0;\n}\n\n.zl-card-nav__link {\n font: inherit;\n line-height: inherit;\n color: var(--zl-link);\n text-underline-offset: 4px;\n cursor: pointer;\n background: none;\n border: none;\n margin: 0;\n padding: 0;\n text-decoration: underline;\n display: inline;\n}\n\n.zl-card-nav__link:hover {\n color: var(--zl-foreground);\n}\n\n.zl-card-nav__link:focus-visible {\n outline: var(--zl-focus-width) solid var(--zl-ring);\n outline-offset: var(--zl-focus-offset);\n}\n\n.zl-card-forgot {\n text-align: right;\n margin: 0;\n}\n\n.zl-card-forgot__link {\n font-family: var(--zl-font-family-sans);\n font-size: var(--zl-text-sm-size);\n line-height: var(--zl-text-sm-leading);\n font-weight: var(--zl-font-weight-normal);\n color: var(--zl-link);\n text-underline-offset: 4px;\n cursor: pointer;\n background: none;\n border: none;\n margin: 0;\n padding: 0;\n text-decoration: none;\n display: inline;\n}\n\n.zl-card-forgot__link:hover {\n text-decoration: underline;\n}\n\n.zl-card-forgot__link:focus-visible {\n outline: var(--zl-focus-width) solid var(--zl-ring);\n outline-offset: var(--zl-focus-offset);\n}\n\n.zl-split {\n grid-template-columns: var(--zl-split-columns, minmax(0, 1fr) minmax(0, 1fr));\n align-items: var(--zl-split-align, center);\n gap: var(--zl-spacing-8);\n width: 100%;\n min-height: 100%;\n display: grid;\n}\n\n.zl-split__brand {\n justify-content: center;\n align-items: center;\n gap: var(--zl-spacing-6);\n padding: var(--zl-spacing-8) var(--zl-spacing-4);\n flex-direction: column;\n display: flex;\n}\n\n.zl-split__logo {\n max-width: min(16rem, 60%);\n max-height: calc(var(--zl-split-logo-max-height, 16rem) * var(--zl-logo-scale, 1));\n}\n\n.zl-split__logo:has( + .zl-split__hero) {\n max-height: calc(var(--zl-split-logo-max-height, 8rem) * var(--zl-logo-scale, 1));\n}\n\n.zl-split__logo:has( + .zl-split__hero) + .zl-split__hero {\n max-height: min(var(--zl-split-hero-max-height, 22.5rem),\n max(6rem,\n calc(100vh - 104px - var(--zl-spacing-8) - var(--zl-spacing-8) -\n 2.5rem - var(--zl-spacing-6) - var(--zl-spacing-6) -\n calc(var(--zl-split-logo-max-height, 8rem) * var(--zl-logo-scale, 1)))));\n}\n\n.zl-split__hero {\n width: 100%;\n max-height: var(--zl-split-hero-max-height, min(22.5rem, 60vh));\n border-radius: var(--zl-radius-lg);\n object-fit: cover;\n}\n\n.zl-split__placeholder {\n aspect-ratio: 4 / 5;\n border-radius: var(--zl-radius-lg);\n background: radial-gradient(120% 120% at 18% 12%,\n var(--zl-gradient-lavender-start) 0%,\n transparent 60%),\n radial-gradient(130% 130% at 85% 92%,\n var(--zl-gradient-rose-start) 0%,\n transparent 60%),\n var(--zl-card);\n width: min(18rem, 85%);\n}\n\n.zl-split__form {\n justify-content: center;\n align-items: center;\n gap: var(--zl-spacing-6);\n flex-direction: column;\n padding: 16px;\n display: flex;\n}\n\n.zl-split__compact {\n max-width: min(12rem, 60%);\n display: none;\n}\n\nimg.zl-split__compact {\n max-height: calc(2.5rem * var(--zl-logo-scale, 1));\n}\n\nimg.zl-split__compact--hero {\n border-radius: var(--zl-radius-lg);\n object-fit: cover;\n width: 100%;\n max-width: 100%;\n max-height: 6rem;\n}\n\n.zl-split--right .zl-split__brand {\n order: 2;\n}\n\n.zl-split--right .zl-split__form {\n order: 1;\n}\n\nzl-page-shell:has(.zl-split) [slot=\"footer\"].zl-attribution {\n grid-template-columns: var(--zl-split-columns, minmax(0, 1fr) minmax(0, 1fr));\n gap: var(--zl-spacing-8);\n width: 100%;\n display: grid;\n}\n\nzl-page-shell:has(.zl-split) [slot=\"footer\"].zl-attribution > * {\n grid-column: 2;\n justify-self: center;\n}\n\nzl-page-shell:has(.zl-split--right) [slot=\"footer\"].zl-attribution > * {\n grid-column: 1;\n}\n\nzl-page-shell:has(.zl-split) [slot=\"footer\"].zl-attribution ::slotted(*) {\n grid-column: 2;\n justify-self: center;\n}\n\nzl-page-shell:has(.zl-split--right) [slot=\"footer\"].zl-attribution ::slotted(*) {\n grid-column: 1;\n}\n\n@container (width <= 48rem) {\n .zl-split {\n grid-template-columns: minmax(0, 1fr);\n }\n\n .zl-split__brand {\n display: var(--zl-split-brand-mobile, none);\n }\n\n .zl-split__compact {\n display: block;\n }\n\n zl-page-shell:has(.zl-split) [slot=\"footer\"].zl-attribution {\n grid-template-columns: minmax(0, 1fr);\n }\n\n zl-page-shell:has(.zl-split) .zl-attribution > *, zl-page-shell:has(.zl-split--right) [slot=\"footer\"].zl-attribution > * {\n grid-column: 1;\n }\n}\n\n.zl-hero {\n text-align: left;\n width: 100%;\n max-width: 34rem;\n color: var(--zl-foreground);\n font-family: var(--zl-font-family-sans);\n flex-direction: column;\n display: flex;\n}\n\n.zl-hero__nav {\n margin-bottom: var(--zl-spacing-8);\n justify-content: space-between;\n align-items: center;\n display: flex;\n}\n\n.zl-hero__brand {\n align-items: center;\n gap: var(--zl-spacing-2);\n letter-spacing: -.01em;\n font-size: 1.125rem;\n font-weight: 700;\n display: inline-flex;\n}\n\n.zl-hero__brand-logo {\n max-height: calc(1.75rem * var(--zl-logo-scale, 1));\n}\n\n.zl-hero__brand-mark {\n border-radius: var(--zl-radius-md);\n background: linear-gradient(135deg,\n var(--zl-gradient-lavender-start),\n var(--zl-gradient-rose-start));\n width: 1.75rem;\n height: 1.75rem;\n display: inline-flex;\n}\n\n.zl-hero__compact-brand {\n overflow-wrap: anywhere;\n letter-spacing: -.01em;\n max-width: 100%;\n color: var(--zl-foreground);\n margin: 0;\n font-size: 1.125rem;\n font-weight: 700;\n}\n\n.zl-hero__nav-links {\n gap: var(--zl-spacing-6);\n color: var(--zl-muted-foreground);\n font-size: .9375rem;\n display: flex;\n}\n\n.zl-hero__nav-links a {\n color: inherit;\n text-decoration: none;\n}\n\n.zl-hero__nav-links a:hover {\n text-decoration: underline;\n}\n\n.zl-hero__eyebrow {\n margin: 0 0 var(--zl-spacing-4);\n letter-spacing: .08em;\n text-transform: uppercase;\n color: var(--zl-muted-foreground);\n font-size: .8125rem;\n font-weight: 600;\n}\n\n.zl-hero__headline {\n margin: 0 0 var(--zl-spacing-4);\n font-family: var(--zl-font-family-heading);\n letter-spacing: -.02em;\n font-size: 2.75rem;\n font-weight: 700;\n line-height: 1.1;\n}\n\n.zl-hero__sub {\n margin: 0 0 var(--zl-spacing-8);\n max-width: 30rem;\n color: var(--zl-muted-foreground);\n font-size: 1.0625rem;\n line-height: 1.6;\n}\n\n.zl-hero__bullets {\n gap: var(--zl-spacing-2);\n margin: 0 0 var(--zl-spacing-8);\n flex-direction: column;\n padding: 0;\n list-style: none;\n display: flex;\n}\n\n.zl-hero__bullets li {\n align-items: center;\n gap: var(--zl-spacing-2);\n font-size: 1rem;\n display: flex;\n}\n\n.zl-hero__bullets li:before {\n content: \"✓\";\n color: var(--zl-success);\n font-weight: 700;\n}\n\n.zl-hero__footnote {\n padding-top: var(--zl-spacing-6);\n border-top: 1px solid var(--zl-border);\n color: var(--zl-muted-foreground);\n margin: 0;\n font-size: .8125rem;\n}\n\n.zl-hero__footnote strong {\n color: var(--zl-muted-foreground);\n font-weight: 600;\n}\n\n.zl-minimal {\n gap: var(--zl-spacing-6);\n flex-direction: column;\n width: min(24rem, 100%);\n margin-inline: auto;\n display: flex;\n}\n\n:host([variant=\"page\"]) .zl-minimal {\n padding: 52px 16px;\n}\n";
13888
14403
  //#endregion
13889
14404
  //#region src/orchestrator/zitadel-login.ts
13890
14405
  /** Narrow a rendered named element to the `formValue` field-atom contract. */
@@ -13993,6 +14508,13 @@ let ZitadelLogin = class ZitadelLogin extends ZitadelSurface {
13993
14508
  set loading(value) {
13994
14509
  this.#_loading_accessor_storage = value;
13995
14510
  }
14511
+ #_completing_accessor_storage = false;
14512
+ get completing() {
14513
+ return this.#_completing_accessor_storage;
14514
+ }
14515
+ set completing(value) {
14516
+ this.#_completing_accessor_storage = value;
14517
+ }
13996
14518
  #_startupError_accessor_storage = null;
13997
14519
  get startupError() {
13998
14520
  return this.#_startupError_accessor_storage;
@@ -14008,6 +14530,13 @@ let ZitadelLogin = class ZitadelLogin extends ZitadelSurface {
14008
14530
  this.#_formValues_accessor_storage = value;
14009
14531
  }
14010
14532
  engine = null;
14533
+ /**
14534
+ * The theme the last commit rendered with. The template output carries the
14535
+ * resolved side's mark, so a theme change rewrites the string and
14536
+ * `unsafeHTML` rebuilds the form — which drops what the visitor had typed
14537
+ * unless it is put back.
14538
+ */
14539
+ lastRenderedTheme = null;
14011
14540
  sanitise = createSanitiser();
14012
14541
  /**
14013
14542
  * Cached compiled tenant template, keyed by source string. Re-rendering on
@@ -14106,9 +14635,14 @@ let ZitadelLogin = class ZitadelLogin extends ZitadelSurface {
14106
14635
  }
14107
14636
  for (const card of this.shadowRoot.querySelectorAll("zl-card")) card.toggleAttribute("data-suppress-header", this.suppressHeader);
14108
14637
  }
14638
+ const previousTheme = this.lastRenderedTheme;
14639
+ this.lastRenderedTheme = this.themeController.theme;
14109
14640
  const props = changed;
14110
- if (!props.has("response")) return;
14111
- this.hydrateStepAfterRender(props.get("response") == null);
14641
+ if (props.has("response")) {
14642
+ this.hydrateStepAfterRender(props.get("response") == null);
14643
+ return;
14644
+ }
14645
+ if (previousTheme !== null && previousTheme !== this.lastRenderedTheme) this.restoreValuesAfterRender();
14112
14646
  }
14113
14647
  /**
14114
14648
  * Apply captured values and move focus once the new step has fully
@@ -14123,11 +14657,19 @@ let ZitadelLogin = class ZitadelLogin extends ZitadelSurface {
14123
14657
  * swaps are user-initiated, so focus moves in both modes.
14124
14658
  */
14125
14659
  async hydrateStepAfterRender(initial = false) {
14660
+ await this.restoreValuesAfterRender();
14661
+ if (!initial || this.variant === "page") this.moveFocusToFirstField(initial && this.variant === "page");
14662
+ }
14663
+ /**
14664
+ * Put captured values back once the rebuilt subtree has rendered. Awaits the
14665
+ * child atoms' own first render before touching them, rather than guessing a
14666
+ * frame with `requestAnimationFrame`.
14667
+ */
14668
+ async restoreValuesAfterRender() {
14126
14669
  await this.updateComplete;
14127
14670
  const atoms = this.shadowRoot?.querySelectorAll("zl-field, zl-select, zl-checkbox, zl-button");
14128
14671
  if (atoms) await Promise.all(Array.from(atoms).map((atom) => atom.updateComplete));
14129
14672
  this.applyValuesToFields();
14130
- if (!initial || this.variant === "page") this.moveFocusToFirstField(initial && this.variant === "page");
14131
14673
  }
14132
14674
  render() {
14133
14675
  if (this.startupError) return b`<form class="zl-mount" novalidate>
@@ -14137,7 +14679,7 @@ let ZitadelLogin = class ZitadelLogin extends ZitadelSurface {
14137
14679
  </zl-card>
14138
14680
  </zl-page-shell>
14139
14681
  </form>`;
14140
- if (!this.response || !this.engine) return b`<slot name="loader"></slot>`;
14682
+ if (!this.response || !this.engine || this.completing) return b`<slot name="loader"></slot>`;
14141
14683
  const rendered = this.injectAttribution(this.renderStep(this.response.step, this.engine));
14142
14684
  return b`<form
14143
14685
  class="zl-mount"
@@ -14169,6 +14711,20 @@ let ZitadelLogin = class ZitadelLogin extends ZitadelSurface {
14169
14711
  return rendered + html;
14170
14712
  }
14171
14713
  /**
14714
+ * The branding a template renders against, with `logo_url` already resolved
14715
+ * to the mark for the active side. Templates read one logo field and get the
14716
+ * right file per surface; the per-side URLs stay on `theme` for a template
14717
+ * that wants to reach them itself.
14718
+ */
14719
+ brandingForTemplate() {
14720
+ if (!this.branding) return {};
14721
+ const logoUrl = resolveLogoUrl(this.branding, this.themeController.theme);
14722
+ return {
14723
+ ...this.branding,
14724
+ logo_url: logoUrl
14725
+ };
14726
+ }
14727
+ /**
14172
14728
  * "Secured with Zitadel" attribution chrome injected into every
14173
14729
  * template's page-shell footer slot. Controlled by
14174
14730
  * `branding.attribution.show_zitadel` — defaults to `true` for
@@ -14181,7 +14737,7 @@ let ZitadelLogin = class ZitadelLogin extends ZitadelSurface {
14181
14737
  const custom = attribution?.custom_link;
14182
14738
  if (!show && !custom) return "";
14183
14739
  const mark = custom ? `<a class="zl-trustmark__mark" part="attribution-mark" href="${escapeHtml(String(custom.href))}">${escapeHtml(String(custom.label))}</a>` : `<a class="zl-trustmark__mark" part="attribution-mark" href="https://zitadel.com" aria-label="Secured with Zitadel">${zitadelTrustmarkInnerHtml()}</a>`;
14184
- return `<div${placement === "footer" ? ` slot="footer"` : ""} part="attribution" class="zl-attribution zl-trustmark">${mark}</div>`;
14740
+ return `<div${placement === "footer" ? ` slot="footer"` : ""} part="attribution" class="zl-attribution zl-trustmark">${mark}<slot name="attribution-trailing"></slot></div>`;
14185
14741
  }
14186
14742
  /** Declarative config read from this element's attributes. */
14187
14743
  get projectAttrs() {
@@ -14229,6 +14785,7 @@ let ZitadelLogin = class ZitadelLogin extends ZitadelSurface {
14229
14785
  }
14230
14786
  applyResponse(wire) {
14231
14787
  this.stepErrorDismissed = false;
14788
+ this.completing = navigatesOnComplete(wire, this.postSignInUrl);
14232
14789
  this.response = wire;
14233
14790
  const { branding, issues } = validateBranding(wire.branding, { renderingOrigin: this.ownerDocument.location.origin });
14234
14791
  this.branding = branding;
@@ -14248,7 +14805,11 @@ let ZitadelLogin = class ZitadelLogin extends ZitadelSurface {
14248
14805
  this.armed = true;
14249
14806
  } else if (!hasBack && this.armed) {
14250
14807
  this.armed = false;
14251
- if (history.state?.zl === true) {
14808
+ if (history.state?.zl === true) if (this.completing) history.replaceState({
14809
+ ...history.state,
14810
+ zl: false
14811
+ }, "");
14812
+ else {
14252
14813
  this.ignoreNextPop = true;
14253
14814
  history.back();
14254
14815
  }
@@ -14320,7 +14881,7 @@ let ZitadelLogin = class ZitadelLogin extends ZitadelSurface {
14320
14881
  messages: [],
14321
14882
  identity: this.deriveIdentity(),
14322
14883
  errors,
14323
- branding: this.branding ?? {},
14884
+ branding: this.brandingForTemplate(),
14324
14885
  loading: this.loading
14325
14886
  };
14326
14887
  let raw;
@@ -14700,6 +15261,7 @@ __decorate([n$1({ attribute: false })], ZitadelLogin.prototype, "locales", null)
14700
15261
  __decorate([r$1()], ZitadelLogin.prototype, "response", null);
14701
15262
  __decorate([r$1()], ZitadelLogin.prototype, "branding", null);
14702
15263
  __decorate([r$1()], ZitadelLogin.prototype, "loading", null);
15264
+ __decorate([r$1()], ZitadelLogin.prototype, "completing", null);
14703
15265
  __decorate([r$1()], ZitadelLogin.prototype, "startupError", null);
14704
15266
  __decorate([r$1()], ZitadelLogin.prototype, "formValues", null);
14705
15267
  ZitadelLogin = __decorate([t$4("zitadel-login")], ZitadelLogin);
@@ -14713,6 +15275,19 @@ ZitadelLogin = __decorate([t$4("zitadel-login")], ZitadelLogin);
14713
15275
  function isAllowedSelectValue(field, value) {
14714
15276
  return field.validation?.enum?.includes(value) ?? false;
14715
15277
  }
15278
+ /**
15279
+ * Whether a terminal step ends in a navigation rather than a screen: a
15280
+ * `redirect` with a URI, or a `show` whose handoff the widget exchanges before
15281
+ * sending the browser to `post-sign-in-url`. A `show` without a
15282
+ * `post-sign-in-url` is the host handling the handoff itself, and that screen
15283
+ * is the flow's own last word — it still renders.
15284
+ */
15285
+ function navigatesOnComplete(wire, postSignInUrl) {
15286
+ const behavior = wire.step.complete;
15287
+ if (behavior === "redirect") return Boolean(wire.redirect_uri);
15288
+ if (behavior === "show") return Boolean(wire.handoff_token && postSignInUrl);
15289
+ return false;
15290
+ }
14716
15291
  function collectInitialValues(step) {
14717
15292
  const values = {};
14718
15293
  if (!step.fields) return values;
@@ -14970,7 +15545,7 @@ let ZitadelLogout = class ZitadelLogout extends i$2 {
14970
15545
  const root = this.shadowRoot;
14971
15546
  if (root && !this.templateMode) {
14972
15547
  applyBaseTokens(root);
14973
- applyBrandingTokens(root, void 0, this.themeController.theme);
15548
+ applyBrandingTokens(root, void 0);
14974
15549
  }
14975
15550
  this.dataset.theme = this.themeController.theme;
14976
15551
  this.toggleAttribute("data-theme-dark", this.themeController.theme === "dark");
@@ -15454,4 +16029,4 @@ __decorate([r$1()], ZitadelSession.prototype, "loading", null);
15454
16029
  __decorate([r$1()], ZitadelSession.prototype, "errorMessage", null);
15455
16030
  ZitadelSession = __decorate([t$4("zitadel-session")], ZitadelSession);
15456
16031
  //#endregion
15457
- export { TEMPLATE_NAMES, ThemeController, ZITADEL_ATTRIBUTION_LOGOTYPE_SVG, ZitadelLogin, ZitadelLogout, ZitadelSession, ZlAlert, ZlButton, ZlCard, ZlCheckbox, ZlField, ZlIcon, ZlPageShell, ZlPill, ZlSelect, applyBrandingTokens, applyFontUrl, baseHostStyles, buildBrandingStylesheet, builtinLocales, businessLocales, createSanitiser, cssVars, de, default_default as defaultTemplate, en, findManifest, focusVisibleStyles, getCurrentStep, it, layout_chrome_default as layoutChromeCss, listKnownTags, manifestRegistry, patchMandatoryGates, resolveTheme, startFlow, submitStep, t, tokens, validateBranding, zitadelTrustmarkInnerHtml, zlAlertManifest, zlButtonManifest, zlCardManifest, zlCheckboxManifest, zlFieldManifest, zlIconManifest, zlPageShellManifest, zlPillManifest, zlSelectManifest };
16032
+ export { TEMPLATE_NAMES, ThemeController, ZITADEL_ATTRIBUTION_LOGOTYPE_SVG, ZitadelLogin, ZitadelLogout, ZitadelSession, ZlAlert, ZlButton, ZlCard, ZlCheckbox, ZlField, ZlIcon, ZlPageShell, ZlPill, ZlSelect, applyBrandingTokens, applyFontUrl, baseHostStyles, buildBrandingStylesheet, builtinLocales, businessLocales, createSanitiser, cssVars, de, default_default as defaultTemplate, en, findManifest, focusVisibleStyles, getCurrentStep, it, layout_chrome_default as layoutChromeCss, listKnownTags, manifestRegistry, patchMandatoryGates, publishedSides, resolveLogoUrl, resolveTheme, startFlow, submitStep, t, tokens, validateBranding, zitadelTrustmarkInnerHtml, zlAlertManifest, zlButtonManifest, zlCardManifest, zlCheckboxManifest, zlFieldManifest, zlIconManifest, zlPageShellManifest, zlPillManifest, zlSelectManifest };