@arcblock/did-connect-service 4.1.12 → 4.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/dist/_generated/asset-bytes.js +1 -1
  2. package/dist/_generated/asset-manifest.js +4 -4
  3. package/dist/_generated/login-bundle-string.d.ts +1 -1
  4. package/dist/_generated/login-bundle-string.d.ts.map +1 -1
  5. package/dist/_generated/login-bundle-string.js +1 -1
  6. package/dist/_generated/login-bundle-string.js.map +1 -1
  7. package/dist/access/change-role.d.ts +48 -0
  8. package/dist/access/change-role.d.ts.map +1 -0
  9. package/dist/access/change-role.js +52 -0
  10. package/dist/access/change-role.js.map +1 -0
  11. package/dist/assets/{admin-core.cbbdbe6d.js → admin-core.a63bbc69.js} +16 -17
  12. package/dist/assets/{admin-extra.35b91f98.js → admin-extra.5b309e5e.js} +8 -4
  13. package/dist/assets/{admin.c26bb17a.css → admin.dfeedbe4.css} +10 -0
  14. package/dist/assets/login.76c483c2.js +720 -0
  15. package/dist/blocklet-service.d.ts.map +1 -1
  16. package/dist/blocklet-service.js +5 -1
  17. package/dist/blocklet-service.js.map +1 -1
  18. package/dist/handler-errors.d.ts +118 -0
  19. package/dist/handler-errors.d.ts.map +1 -0
  20. package/dist/handler-errors.js +38 -0
  21. package/dist/handler-errors.js.map +1 -0
  22. package/dist/handlers/access-key-connect-handler.d.ts +3 -0
  23. package/dist/handlers/access-key-connect-handler.d.ts.map +1 -1
  24. package/dist/handlers/access-key-connect-handler.js +7 -1
  25. package/dist/handlers/access-key-connect-handler.js.map +1 -1
  26. package/dist/handlers/admin-instance-handler.d.ts.map +1 -1
  27. package/dist/handlers/admin-instance-handler.js +2 -1
  28. package/dist/handlers/admin-instance-handler.js.map +1 -1
  29. package/dist/handlers/auth-handler.d.ts.map +1 -1
  30. package/dist/handlers/auth-handler.js +33 -9
  31. package/dist/handlers/auth-handler.js.map +1 -1
  32. package/dist/handlers/branding-handler.d.ts.map +1 -1
  33. package/dist/handlers/branding-handler.js +12 -9
  34. package/dist/handlers/branding-handler.js.map +1 -1
  35. package/dist/handlers/did-connect-handler.d.ts.map +1 -1
  36. package/dist/handlers/did-connect-handler.js +6 -5
  37. package/dist/handlers/did-connect-handler.js.map +1 -1
  38. package/dist/handlers/email-login-handler.d.ts +1 -1
  39. package/dist/handlers/email-login-handler.d.ts.map +1 -1
  40. package/dist/handlers/email-login-handler.js +63 -12
  41. package/dist/handlers/email-login-handler.js.map +1 -1
  42. package/dist/handlers/federation-admin-handler.d.ts +2 -0
  43. package/dist/handlers/federation-admin-handler.d.ts.map +1 -1
  44. package/dist/handlers/federation-admin-handler.js +105 -51
  45. package/dist/handlers/federation-admin-handler.js.map +1 -1
  46. package/dist/handlers/oauth-handler.d.ts.map +1 -1
  47. package/dist/handlers/oauth-handler.js +23 -12
  48. package/dist/handlers/oauth-handler.js.map +1 -1
  49. package/dist/handlers/passkey-handler.d.ts.map +1 -1
  50. package/dist/handlers/passkey-handler.js +5 -4
  51. package/dist/handlers/passkey-handler.js.map +1 -1
  52. package/dist/handlers/team-handler.d.ts.map +1 -1
  53. package/dist/handlers/team-handler.js +19 -19
  54. package/dist/handlers/team-handler.js.map +1 -1
  55. package/dist/identity/refresh-tokens.d.ts +21 -0
  56. package/dist/identity/refresh-tokens.d.ts.map +1 -0
  57. package/dist/identity/refresh-tokens.js +45 -0
  58. package/dist/identity/refresh-tokens.js.map +1 -0
  59. package/dist/identity/session-context.d.ts +12 -0
  60. package/dist/identity/session-context.d.ts.map +1 -1
  61. package/dist/identity/session-context.js +12 -0
  62. package/dist/identity/session-context.js.map +1 -1
  63. package/dist/identity/session-tokens.d.ts +15 -0
  64. package/dist/identity/session-tokens.d.ts.map +1 -0
  65. package/dist/identity/session-tokens.js +15 -0
  66. package/dist/identity/session-tokens.js.map +1 -0
  67. package/dist/locale.d.ts +1 -1
  68. package/dist/locale.d.ts.map +1 -1
  69. package/dist/og/templates.d.ts.map +1 -1
  70. package/dist/og/templates.js +33 -4
  71. package/dist/og/templates.js.map +1 -1
  72. package/dist/pages/admin/i18n.d.ts.map +1 -1
  73. package/dist/pages/admin/i18n.js +33 -9
  74. package/dist/pages/admin/i18n.js.map +1 -1
  75. package/dist/pages/admin/scripts/api-client.d.ts +1 -1
  76. package/dist/pages/admin/scripts/api-client.d.ts.map +1 -1
  77. package/dist/pages/admin/scripts/api-client.js +4 -4
  78. package/dist/pages/admin/scripts/utils.d.ts +1 -1
  79. package/dist/pages/admin/scripts/utils.d.ts.map +1 -1
  80. package/dist/pages/admin/scripts/utils.js +7 -8
  81. package/dist/pages/admin/scripts/utils.js.map +1 -1
  82. package/dist/pages/admin/styles.d.ts +1 -1
  83. package/dist/pages/admin/styles.d.ts.map +1 -1
  84. package/dist/pages/admin/styles.js +10 -0
  85. package/dist/pages/admin/styles.js.map +1 -1
  86. package/dist/pages/admin/tab-access-keys.d.ts +1 -1
  87. package/dist/pages/admin/tab-access-keys.d.ts.map +1 -1
  88. package/dist/pages/admin/tab-access-keys.js +4 -4
  89. package/dist/pages/admin/tab-invitations.d.ts +1 -1
  90. package/dist/pages/admin/tab-invitations.d.ts.map +1 -1
  91. package/dist/pages/admin/tab-invitations.js +2 -2
  92. package/dist/pages/admin/tab-members.d.ts +1 -1
  93. package/dist/pages/admin/tab-members.d.ts.map +1 -1
  94. package/dist/pages/admin/tab-members.js +7 -3
  95. package/dist/pages/admin/tab-members.js.map +1 -1
  96. package/dist/pages/admin/tab-profile-accounts.js +1 -1
  97. package/dist/pages/admin-instances-page.d.ts +1 -1
  98. package/dist/pages/admin-instances-page.d.ts.map +1 -1
  99. package/dist/pages/admin-instances-page.js +195 -65
  100. package/dist/pages/admin-instances-page.js.map +1 -1
  101. package/dist/pages/gen-access-key-page.d.ts +1 -1
  102. package/dist/pages/gen-access-key-page.d.ts.map +1 -1
  103. package/dist/pages/gen-access-key-page.js +163 -35
  104. package/dist/pages/gen-access-key-page.js.map +1 -1
  105. package/dist/pages/homepage.d.ts.map +1 -1
  106. package/dist/pages/homepage.js +0 -4
  107. package/dist/pages/homepage.js.map +1 -1
  108. package/dist/pages/invite-page.d.ts +1 -1
  109. package/dist/pages/invite-page.d.ts.map +1 -1
  110. package/dist/pages/invite-page.js +91 -21
  111. package/dist/pages/invite-page.js.map +1 -1
  112. package/dist/pages/oauth-callback-page.d.ts.map +1 -1
  113. package/dist/pages/oauth-callback-page.js +7 -3
  114. package/dist/pages/oauth-callback-page.js.map +1 -1
  115. package/dist/store/d1-store.d.ts +55 -1
  116. package/dist/store/d1-store.d.ts.map +1 -1
  117. package/dist/store/d1-store.js +162 -9
  118. package/dist/store/d1-store.js.map +1 -1
  119. package/migrations/0011_refresh_tokens.sql +23 -0
  120. package/package.json +7 -7
  121. package/dist/assets/admin-core.a13c2729.js +0 -1461
  122. package/dist/assets/admin-core.c0b5af61.js +0 -1393
  123. package/dist/assets/admin-core.e83fd379.js +0 -1397
  124. package/dist/assets/admin-extra.7ca9c16b.js +0 -2529
  125. package/dist/assets/admin-extra.841b2bbe.js +0 -2530
  126. package/dist/assets/admin-extra.afcb3651.js +0 -2528
  127. package/dist/assets/login.d3f05790.js +0 -720
@@ -0,0 +1,52 @@
1
+ const ROLE_LEVEL = { owner: 3, admin: 2, member: 1, guest: 0 };
2
+ const ASSIGNABLE_ROLES = new Set(["admin", "member", "guest"]);
3
+ export async function changeMembershipRole(params) {
4
+ const { store, instanceDid, callerDid, callerRole, targetDid, newRole, ip } = params;
5
+ if (!newRole || !ASSIGNABLE_ROLES.has(newRole)) {
6
+ return {
7
+ ok: false,
8
+ status: 400,
9
+ code: "VALIDATION_ERROR",
10
+ message: "Invalid role. Must be 'admin', 'member', or 'guest'",
11
+ };
12
+ }
13
+ if (targetDid === callerDid) {
14
+ return { ok: false, status: 409, code: "CONFLICT", message: "Cannot change your own role" };
15
+ }
16
+ // Only owner can assign admin role
17
+ if (newRole === "admin" && callerRole !== "owner") {
18
+ return {
19
+ ok: false,
20
+ status: 403,
21
+ code: "FORBIDDEN",
22
+ message: "Only the owner can assign admin role",
23
+ };
24
+ }
25
+ const membership = await store.getMembership(targetDid, instanceDid);
26
+ if (!membership) {
27
+ return { ok: false, status: 404, code: "NOT_FOUND", message: "Member not found" };
28
+ }
29
+ const oldRole = membership.role;
30
+ const callerLevel = ROLE_LEVEL[callerRole] ?? 0;
31
+ const targetLevel = ROLE_LEVEL[oldRole] ?? 0;
32
+ // Cannot modify someone at same or higher level (admin can't change admin/owner)
33
+ if (targetLevel >= callerLevel) {
34
+ return {
35
+ ok: false,
36
+ status: 403,
37
+ code: "FORBIDDEN",
38
+ message: "Cannot change the role of a member with equal or higher privileges",
39
+ };
40
+ }
41
+ await store.updateMembershipRole(targetDid, instanceDid, newRole, callerDid);
42
+ await store.createAuditLog({
43
+ action: "user.role_change",
44
+ operatorDid: callerDid,
45
+ targetDid,
46
+ metadata: { oldRole, newRole },
47
+ ip,
48
+ instanceDid,
49
+ });
50
+ return { ok: true, oldRole };
51
+ }
52
+ //# sourceMappingURL=change-role.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"change-role.js","sourceRoot":"","sources":["../../src/access/change-role.ts"],"names":[],"mappings":"AAwBA,MAAM,UAAU,GAA2B,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;AACvF,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;AAqB/D,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,MAAkC;IAElC,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,MAAM,CAAC;IAErF,IAAI,CAAC,OAAO,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;QAC/C,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,GAAG;YACX,IAAI,EAAE,kBAAkB;YACxB,OAAO,EAAE,qDAAqD;SAC/D,CAAC;IACJ,CAAC;IAED,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,6BAA6B,EAAE,CAAC;IAC9F,CAAC;IAED,mCAAmC;IACnC,IAAI,OAAO,KAAK,OAAO,IAAI,UAAU,KAAK,OAAO,EAAE,CAAC;QAClD,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,GAAG;YACX,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,sCAAsC;SAChD,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,KAAK,CAAC,aAAa,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IACrE,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC;IACpF,CAAC;IAED,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC;IAChC,MAAM,WAAW,GAAG,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IAChD,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7C,iFAAiF;IACjF,IAAI,WAAW,IAAI,WAAW,EAAE,CAAC;QAC/B,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,GAAG;YACX,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,oEAAoE;SAC9E,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,CAAC,oBAAoB,CAAC,SAAS,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;IAC7E,MAAM,KAAK,CAAC,cAAc,CAAC;QACzB,MAAM,EAAE,kBAAkB;QAC1B,WAAW,EAAE,SAAS;QACtB,SAAS;QACT,QAAQ,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE;QAC9B,EAAE;QACF,WAAW;KACZ,CAAC,CAAC;IAEH,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AAC/B,CAAC"}
@@ -35,12 +35,11 @@ function relativeTime(iso) {
35
35
  var h = Math.floor(m / 60);
36
36
  var d = Math.floor(h / 24);
37
37
  var label;
38
- if (d > 0) label = d + "d " + (h % 24) + "h";
39
- else if (h > 0) label = h + "h " + (m % 60) + "m";
40
- else if (m > 0) label = m + " min";
41
- else label = "just now";
42
- if (label === "just now") return label;
43
- return future ? "in " + label : label + " ago";
38
+ if (d > 0) label = __t("time.dh", { d: d, h: (h % 24) });
39
+ else if (h > 0) label = __t("time.hm", { h: h, m: (m % 60) });
40
+ else if (m > 0) label = __t("time.min", { m: m });
41
+ else return __t("time.justNow");
42
+ return future ? __t("time.in", { label: label }) : __t("time.ago", { label: label });
44
43
  }
45
44
 
46
45
  function absoluteTime(iso) {
@@ -53,7 +52,7 @@ function absoluteTime(iso) {
53
52
  function copyToClipboard(text) {
54
53
  if (navigator.clipboard && navigator.clipboard.writeText) {
55
54
  navigator.clipboard.writeText(text).then(function() {
56
- showToast("Copied!", "success");
55
+ showToast(__t("common.copied"), "success");
57
56
  }).catch(function() {
58
57
  fallbackCopy(text);
59
58
  });
@@ -69,7 +68,7 @@ function fallbackCopy(text) {
69
68
  input.select();
70
69
  document.execCommand("copy");
71
70
  input.remove();
72
- showToast("Copied!", "success");
71
+ showToast(__t("common.copied"), "success");
73
72
  }
74
73
 
75
74
  // Delegate to the canonical implementation from @arcblock/did-connect-core
@@ -154,19 +153,19 @@ async function api(method, path, body, callOpts) {
154
153
 
155
154
  if (res.status === 401) {
156
155
  location.href = "/.well-known/service/api/did/logout";
157
- return { ok: false, error: "Session expired", code: "UNAUTHENTICATED" };
156
+ return { ok: false, error: __t("common.sessionExpired"), code: "UNAUTHENTICATED" };
158
157
  }
159
158
  if (res.status === 403 && data.code === "BLOCKED") {
160
159
  location.href = "/.well-known/service/api/did/logout";
161
- return { ok: false, error: "Account blocked", code: "BLOCKED" };
160
+ return { ok: false, error: __t("common.accountBlocked"), code: "BLOCKED" };
162
161
  }
163
162
  if (!res.ok) {
164
- if (!callOpts.silent) showToast(data.error || "Request failed", "error");
163
+ if (!callOpts.silent) showToast(data.error || __t("common.requestFailed"), "error");
165
164
  return { ok: false, error: data.error, code: data.code };
166
165
  }
167
166
  return data;
168
167
  } catch (err) {
169
- if (!callOpts.silent) showToast("Network error", "error");
168
+ if (!callOpts.silent) showToast(__t("common.networkError"), "error");
170
169
  return { ok: false, error: err.message, code: "NETWORK_ERROR" };
171
170
  }
172
171
  }
@@ -848,7 +847,7 @@ function connectOAuth(provider) {
848
847
  var left = (screen.width - w) / 2, top = (screen.height - h) / 2;
849
848
  var popup = window.open(url, "oauth_bind", "width=" + w + ",height=" + h + ",left=" + left + ",top=" + top);
850
849
  if (!popup) {
851
- showToast("Popup blocked — please allow popups and try again", "error");
850
+ showToast(__t("common.popupBlocked"), "error");
852
851
  return;
853
852
  }
854
853
 
@@ -1233,10 +1232,10 @@ function renderAccessKeys(keys, paging) {
1233
1232
 
1234
1233
  function openCreateAkDialog() {
1235
1234
  var callerRole = __caller.role;
1236
- var roleOptions = '<option value="guest">Guest</option>';
1237
- if (callerRole === "member" || callerRole === "admin" || callerRole === "owner") roleOptions += '<option value="member"' + (callerRole === "member" ? ' selected' : '') + '>Member</option>';
1238
- if (callerRole === "admin" || callerRole === "owner") roleOptions += '<option value="admin"' + (callerRole === "admin" ? ' selected' : '') + '>Admin</option>';
1239
- if (callerRole === "owner") roleOptions += '<option value="owner">Owner</option>';
1235
+ var roleOptions = '<option value="guest">' + __t("common.guest") + '</option>';
1236
+ if (callerRole === "member" || callerRole === "admin" || callerRole === "owner") roleOptions += '<option value="member"' + (callerRole === "member" ? ' selected' : '') + '>' + __t("common.member") + '</option>';
1237
+ if (callerRole === "admin" || callerRole === "owner") roleOptions += '<option value="admin"' + (callerRole === "admin" ? ' selected' : '') + '>' + __t("common.admin") + '</option>';
1238
+ if (callerRole === "owner") roleOptions += '<option value="owner">' + __t("common.owner") + '</option>';
1240
1239
 
1241
1240
  var expiryOptions = '<option value="">' + __t("accessKeys.expirations.never") + '</option><option value="7d">' + __t("accessKeys.expirations.days7") + '</option><option value="30d">' + __t("accessKeys.expirations.days30") + '</option><option value="90d">' + __t("accessKeys.expirations.days90") + '</option><option value="custom">' + __t("accessKeys.expirations.custom") + '</option>';
1242
1241
 
@@ -158,8 +158,12 @@ async function loadMembers(page) {
158
158
 
159
159
  function renderMembersTable(users, paging) {
160
160
  var wrap = document.getElementById("members-table-wrap");
161
+ // Aggregated (cross-instance "All Instances") view can't tell which instance
162
+ // a role change should apply to, so per-row actions are disabled there — see
163
+ // canOperate below. Surface why, instead of silently hiding the buttons.
164
+ var aggregatedHint = __membersAggregated ? '<p class="text-muted" style="font-size:13px;margin-bottom:12px;">' + __t("members.aggregatedHint") + '</p>' : '';
161
165
  if (!users || users.length === 0) {
162
- wrap.innerHTML = '<div class="empty-state"><div class="empty-state-icon"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/></svg></div><div class="empty-state-title">' + __t("members.noMembers") + '</div><div class="empty-state-desc">' + __t("members.noMembersHint") + '</div></div>';
166
+ wrap.innerHTML = aggregatedHint + '<div class="empty-state"><div class="empty-state-icon"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/></svg></div><div class="empty-state-title">' + __t("members.noMembers") + '</div><div class="empty-state-desc">' + __t("members.noMembersHint") + '</div></div>';
163
167
  document.getElementById("members-pagination").innerHTML = "";
164
168
  return;
165
169
  }
@@ -167,7 +171,7 @@ function renderMembersTable(users, paging) {
167
171
  var instanceSel = document.getElementById("members-instance-filter");
168
172
  var instanceVisible = instanceSel && instanceSel.style.display !== 'none';
169
173
  var showInstance = instanceVisible && instanceSel.value === "all";
170
- var html = '<table class="table"><thead><tr>';
174
+ var html = aggregatedHint + '<table class="table"><thead><tr>';
171
175
  html += sortableTh("name", __t("members.col.member"));
172
176
  html += sortableTh("role", __t("members.col.role"));
173
177
  if (showInstance) html += '<th>' + __t("members.col.instance") + '</th>';
@@ -521,7 +525,7 @@ function renderInvitationsTable(invitations, paging) {
521
525
  var uses = inv.useCount + " / " + inv.maxUses;
522
526
  if (inv.useCount >= inv.maxUses) uses += ' <span class="badge badge-blocked">' + __t("invitations.closed") + '</span>';
523
527
  var expired = new Date(inv.expireAt) < new Date();
524
- var expireText = expired ? '<span class="badge badge-expired">expired</span>' : '<span class="cell-date">' + escapeHtml(relativeTime(inv.expireAt)) + '</span>';
528
+ var expireText = expired ? '<span class="badge badge-expired">' + __t("common.expired") + '</span>' : '<span class="cell-date">' + escapeHtml(relativeTime(inv.expireAt)) + '</span>';
525
529
  var isActive = !expired && inv.status !== "closed" && inv.useCount < inv.maxUses;
526
530
  var inviter = inv.inviterName ? escapeHtml(inv.inviterName) : '<span class="text-muted">—</span>';
527
531
 
@@ -580,7 +584,7 @@ async function deleteInvitation(id) {
580
584
  function initInviteRoleOptions() {
581
585
  var select = document.getElementById("invite-role");
582
586
  if (__caller.role === "owner") {
583
- select.innerHTML = '<option value="guest">Guest</option><option value="member">Member</option><option value="admin">Admin</option>';
587
+ select.innerHTML = '<option value="guest">' + __t("common.guest") + '</option><option value="member">' + __t("common.member") + '</option><option value="admin">' + __t("common.admin") + '</option>';
584
588
  }
585
589
  }
586
590
 
@@ -1157,11 +1157,20 @@
1157
1157
  max-width: 520px;
1158
1158
  width: 90%;
1159
1159
  box-shadow: var(--shadow-lg);
1160
+ max-height: calc(100vh - 32px);
1161
+ max-height: calc(100dvh - 32px);
1160
1162
  overflow: hidden;
1161
1163
  animation: modalIn 0.2s ease;
1162
1164
  }
1165
+ dialog[open] {
1166
+ display: flex;
1167
+ flex-direction: column;
1168
+ }
1163
1169
  dialog::backdrop { background: rgba(0,0,0,0.7); }
1164
1170
  .dialog-content {
1171
+ min-height: 0;
1172
+ overflow-y: auto;
1173
+ overscroll-behavior: contain;
1165
1174
  padding: 24px;
1166
1175
  }
1167
1176
  .dialog-content h3 {
@@ -1179,6 +1188,7 @@
1179
1188
  }
1180
1189
  .dialog-actions {
1181
1190
  display: flex;
1191
+ flex: 0 0 auto;
1182
1192
  justify-content: flex-end;
1183
1193
  gap: 8px;
1184
1194
  padding: 16px 24px;