@tangle-network/agent-app 0.60.184 → 0.60.185

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 (81) hide show
  1. package/dist/agent-profiles/d1.js +3 -3
  2. package/dist/agent-profiles/index.js +3 -3
  3. package/dist/assistant/index.js +9 -9
  4. package/dist/channels/index.js +1 -1
  5. package/dist/chat-react/index.js +4 -4
  6. package/dist/chat-routes/index.js +4 -4
  7. package/dist/{chunk-YXLN4PXK.js → chunk-2RCPPOQ4.js} +2 -2
  8. package/dist/{chunk-YESFGM56.js → chunk-5IS3GCMR.js} +3 -3
  9. package/dist/{chunk-3RHAF2P5.js → chunk-A3CUVYRZ.js} +3 -3
  10. package/dist/{chunk-LN5RBVGP.js → chunk-AVP5SNIG.js} +4 -4
  11. package/dist/{chunk-LN5RBVGP.js.map → chunk-AVP5SNIG.js.map} +1 -1
  12. package/dist/{chunk-7BYY7QKC.js → chunk-E65YV7VV.js} +5 -5
  13. package/dist/{chunk-7BYY7QKC.js.map → chunk-E65YV7VV.js.map} +1 -1
  14. package/dist/chunk-EZFBGW23.js +179 -0
  15. package/dist/chunk-EZFBGW23.js.map +1 -0
  16. package/dist/{chunk-UDLA3NGY.js → chunk-GNBBY3KN.js} +3 -3
  17. package/dist/{chunk-KOTNMTHV.js → chunk-HCFVEJQL.js} +3 -3
  18. package/dist/{chunk-YGBEGBJY.js → chunk-HEPEGY6Q.js} +2 -2
  19. package/dist/{chunk-AJZ3EJB5.js → chunk-IJAXOQJU.js} +2 -2
  20. package/dist/{chunk-EQRVOMMS.js → chunk-KMIDRUUH.js} +2 -2
  21. package/dist/{chunk-BBHVV37T.js → chunk-MSAGTFN4.js} +4 -4
  22. package/dist/chunk-OBKVNZPH.js +70 -0
  23. package/dist/chunk-OBKVNZPH.js.map +1 -0
  24. package/dist/{chunk-75ZIWV4U.js → chunk-QAF7ZTO3.js} +4 -4
  25. package/dist/{chunk-TJ4DYKF2.js → chunk-XG6FODYO.js} +3 -2
  26. package/dist/chunk-XG6FODYO.js.map +1 -0
  27. package/dist/{chunk-6O2NQ7WD.js → chunk-Y2SOQOJE.js} +3 -3
  28. package/dist/integrations-react/index.js +1 -1
  29. package/dist/{mention-editor-ZJAOUDEE.js → mention-editor-NOS5R3AU.js} +2 -2
  30. package/dist/preset-cloudflare/index.js +2 -2
  31. package/dist/sandbox/index.js +4 -4
  32. package/dist/settings-react/confirm-action.d.ts +51 -0
  33. package/dist/settings-react/delete-workspace-section.d.ts +31 -0
  34. package/dist/settings-react/export-section.d.ts +26 -0
  35. package/dist/settings-react/index.d.ts +20 -0
  36. package/dist/settings-react/index.js +1438 -0
  37. package/dist/settings-react/index.js.map +1 -0
  38. package/dist/settings-react/members-section.d.ts +66 -0
  39. package/dist/settings-react/save-status.d.ts +24 -0
  40. package/dist/settings-react/setting-outcome.d.ts +33 -0
  41. package/dist/settings-react/settings-layout.d.ts +71 -0
  42. package/dist/settings-react/settings-save-bar.d.ts +26 -0
  43. package/dist/settings-react/timezone-picker.d.ts +17 -0
  44. package/dist/settings-react/use-setting-field.d.ts +70 -0
  45. package/dist/stories/settings/settings-backend.d.ts +66 -0
  46. package/dist/stories/settings/settings-demo.d.ts +16 -0
  47. package/dist/stories/settings/team-panels-demo.d.ts +21 -0
  48. package/dist/studio-react/index.js +2 -2
  49. package/dist/teams-react/components/InviteForm.d.ts +2 -2
  50. package/dist/teams-react/components/MembersPanel.d.ts +9 -5
  51. package/dist/teams-react/components/RoleSelect.d.ts +10 -8
  52. package/dist/teams-react/components/invitation-detail.d.ts +8 -0
  53. package/dist/teams-react/components/member-access.d.ts +17 -0
  54. package/dist/teams-react/contracts.d.ts +2 -0
  55. package/dist/teams-react/index.js +119 -242
  56. package/dist/teams-react/index.js.map +1 -1
  57. package/dist/theme/tokens.css +43 -2
  58. package/dist/tools/index.js +4 -4
  59. package/dist/web-react/agent-profile-editor.js +4 -4
  60. package/dist/web-react/async/index.js +1 -1
  61. package/dist/web-react/controls.d.ts +4 -5
  62. package/dist/web-react/index.js +12 -12
  63. package/dist/work-product/index.js +2 -2
  64. package/dist/workspace-export/react.d.ts +26 -0
  65. package/dist/workspace-export/react.js +6 -169
  66. package/dist/workspace-export/react.js.map +1 -1
  67. package/dist/workspace-react/index.js +3 -3
  68. package/package.json +9 -4
  69. package/dist/chunk-TJ4DYKF2.js.map +0 -1
  70. /package/dist/{chunk-YXLN4PXK.js.map → chunk-2RCPPOQ4.js.map} +0 -0
  71. /package/dist/{chunk-YESFGM56.js.map → chunk-5IS3GCMR.js.map} +0 -0
  72. /package/dist/{chunk-3RHAF2P5.js.map → chunk-A3CUVYRZ.js.map} +0 -0
  73. /package/dist/{chunk-UDLA3NGY.js.map → chunk-GNBBY3KN.js.map} +0 -0
  74. /package/dist/{chunk-KOTNMTHV.js.map → chunk-HCFVEJQL.js.map} +0 -0
  75. /package/dist/{chunk-YGBEGBJY.js.map → chunk-HEPEGY6Q.js.map} +0 -0
  76. /package/dist/{chunk-AJZ3EJB5.js.map → chunk-IJAXOQJU.js.map} +0 -0
  77. /package/dist/{chunk-EQRVOMMS.js.map → chunk-KMIDRUUH.js.map} +0 -0
  78. /package/dist/{chunk-BBHVV37T.js.map → chunk-MSAGTFN4.js.map} +0 -0
  79. /package/dist/{chunk-75ZIWV4U.js.map → chunk-QAF7ZTO3.js.map} +0 -0
  80. /package/dist/{chunk-6O2NQ7WD.js.map → chunk-Y2SOQOJE.js.map} +0 -0
  81. /package/dist/{mention-editor-ZJAOUDEE.js.map → mention-editor-NOS5R3AU.js.map} +0 -0
@@ -1,3 +1,10 @@
1
+ import {
2
+ RoleSelect,
3
+ canManageMember,
4
+ invitationDetail,
5
+ isCurrentMember,
6
+ memberRoleLabel
7
+ } from "../chunk-OBKVNZPH.js";
1
8
  import {
2
9
  INVITATION_EMAIL_HINT,
3
10
  isInvitationEmail
@@ -6,94 +13,19 @@ import {
6
13
  hasWorkspaceRole
7
14
  } from "../chunk-6XIAPIW6.js";
8
15
 
16
+ // src/teams-react/components/MembersPanel.tsx
17
+ import { Button as Button2, Heading } from "@tangle-network/ui/primitives";
18
+
9
19
  // src/teams-react/components/InviteForm.tsx
10
20
  import { Button, Input } from "@tangle-network/ui/primitives";
11
- import { useId, useState as useState2 } from "react";
12
-
13
- // src/teams-react/components/RoleSelect.tsx
14
- import { useEffect, useRef, useState } from "react";
21
+ import { useId, useState } from "react";
15
22
  import { jsx, jsxs } from "react/jsx-runtime";
16
- var ASSIGNABLE_ROLES = [
17
- { value: "viewer", label: "Viewer", description: "Can read everything" },
18
- { value: "editor", label: "Editor", description: "Can also chat and edit" },
19
- { value: "admin", label: "Admin", description: "Can also manage settings and people" }
20
- ];
21
- function ChevronDownGlyph({ className }) {
22
- return /* @__PURE__ */ jsx("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" }) });
23
- }
24
- function RoleSelect({ value, onChange, ariaLabel, disabled }) {
25
- const [open, setOpen] = useState(false);
26
- const ref = useRef(null);
27
- const current = ASSIGNABLE_ROLES.find((option) => option.value === value);
28
- useEffect(() => {
29
- if (!open) return;
30
- function onDocPointer(event) {
31
- if (ref.current && !ref.current.contains(event.target)) setOpen(false);
32
- }
33
- function onKey(event) {
34
- if (event.key === "Escape") setOpen(false);
35
- }
36
- document.addEventListener("pointerdown", onDocPointer);
37
- document.addEventListener("keydown", onKey);
38
- return () => {
39
- document.removeEventListener("pointerdown", onDocPointer);
40
- document.removeEventListener("keydown", onKey);
41
- };
42
- }, [open]);
43
- return /* @__PURE__ */ jsxs("div", { ref, className: "relative", children: [
44
- /* @__PURE__ */ jsxs(
45
- "button",
46
- {
47
- type: "button",
48
- disabled,
49
- "aria-haspopup": "listbox",
50
- "aria-expanded": open,
51
- "aria-label": ariaLabel,
52
- onClick: () => setOpen((v) => !v),
53
- className: "flex items-center justify-between gap-1.5 rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-2 py-1.5 text-sm text-[var(--text-secondary)] hover:border-[var(--brand-primary)] disabled:cursor-default disabled:opacity-40",
54
- children: [
55
- /* @__PURE__ */ jsx("span", { children: current?.label ?? value }),
56
- /* @__PURE__ */ jsx(ChevronDownGlyph, { className: "h-3 w-3 text-[var(--text-muted)]" })
57
- ]
58
- }
59
- ),
60
- open ? /* @__PURE__ */ jsx(
61
- "div",
62
- {
63
- role: "listbox",
64
- "aria-label": ariaLabel,
65
- className: "absolute right-0 top-full z-50 mt-1 flex w-64 flex-col rounded border border-[var(--card-edge)] bg-[hsl(var(--popover))] py-1 shadow-[var(--shadow-overlay)]",
66
- children: ASSIGNABLE_ROLES.map((option) => /* @__PURE__ */ jsxs(
67
- "button",
68
- {
69
- type: "button",
70
- role: "option",
71
- "aria-selected": option.value === value,
72
- onClick: () => {
73
- onChange(option.value);
74
- setOpen(false);
75
- },
76
- className: `flex flex-col px-3 py-2 text-left text-sm hover:bg-[color-mix(in_srgb,var(--brand-primary)_10%,transparent)] ${option.value === value ? "text-[var(--brand-primary)]" : "text-[var(--text-primary)]"}`,
77
- children: [
78
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: option.label }),
79
- /* @__PURE__ */ jsx("span", { className: "text-[var(--text-muted)]", children: option.description })
80
- ]
81
- },
82
- option.value
83
- ))
84
- }
85
- ) : null
86
- ] });
87
- }
88
-
89
- // src/teams-react/components/InviteForm.tsx
90
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
91
23
  function InviteForm({ label, placeholder, onInvite }) {
92
24
  const id = useId();
93
- const [email, setEmail] = useState2("");
94
- const [role, setRole] = useState2("editor");
95
- const [inviting, setInviting] = useState2(false);
96
- const [checked, setChecked] = useState2(false);
25
+ const [email, setEmail] = useState("");
26
+ const [role, setRole] = useState("editor");
27
+ const [inviting, setInviting] = useState(false);
28
+ const [checked, setChecked] = useState(false);
97
29
  const valid = isInvitationEmail(email);
98
30
  const showHint = checked && email.trim() !== "" && !valid;
99
31
  async function submit() {
@@ -112,10 +44,10 @@ function InviteForm({ label, placeholder, onInvite }) {
112
44
  setInviting(false);
113
45
  }
114
46
  }
115
- return /* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-1.5", children: [
116
- /* @__PURE__ */ jsx2("label", { htmlFor: `${id}-email`, className: "text-sm font-medium text-[var(--text-secondary)]", children: label }),
117
- /* @__PURE__ */ jsxs2("div", { className: "flex flex-wrap gap-2", children: [
118
- /* @__PURE__ */ jsx2(
47
+ return /* @__PURE__ */ jsxs("div", { className: "@container/invite flex flex-col gap-1.5", children: [
48
+ /* @__PURE__ */ jsx("label", { htmlFor: `${id}-email`, className: "text-sm font-medium text-[var(--text-secondary)]", children: label }),
49
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
50
+ /* @__PURE__ */ jsx(
119
51
  Input,
120
52
  {
121
53
  id: `${id}-email`,
@@ -137,20 +69,19 @@ function InviteForm({ label, placeholder, onInvite }) {
137
69
  className: "min-w-[12rem] flex-1"
138
70
  }
139
71
  ),
140
- /* @__PURE__ */ jsxs2("div", { className: "ml-auto flex shrink-0 gap-2", children: [
141
- /* @__PURE__ */ jsx2(RoleSelect, { value: role, ariaLabel: "Invite role", onChange: setRole }),
142
- /* @__PURE__ */ jsx2(Button, { type: "button", onClick: () => void submit(), disabled: inviting || !valid, children: inviting ? "Inviting\u2026" : "Invite" })
143
- ] })
72
+ /* @__PURE__ */ jsx("div", { className: "w-72 shrink-0 @max-[36rem]/invite:w-full", children: /* @__PURE__ */ jsx(RoleSelect, { value: role, ariaLabel: "Invite role", onChange: setRole }) }),
73
+ /* @__PURE__ */ jsx(Button, { type: "button", className: "ml-auto", onClick: () => void submit(), disabled: inviting || !valid, children: inviting ? "Inviting\u2026" : "Invite" })
144
74
  ] }),
145
- showHint && /* @__PURE__ */ jsx2("p", { id: `${id}-hint`, role: "alert", className: "text-sm text-[var(--surface-danger-text)]", children: INVITATION_EMAIL_HINT })
75
+ showHint && /* @__PURE__ */ jsx("p", { id: `${id}-hint`, role: "alert", className: "text-sm text-[var(--surface-danger-text)]", children: INVITATION_EMAIL_HINT })
146
76
  ] });
147
77
  }
148
78
 
149
79
  // src/teams-react/components/MembersPanel.tsx
150
- import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
80
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
151
81
  function MembersPanel({
152
82
  members,
153
83
  currentRole,
84
+ currentUserId,
154
85
  onInvite,
155
86
  onChangeRole,
156
87
  onRemove,
@@ -187,73 +118,67 @@ function MembersPanel({
187
118
  notify("error", err instanceof Error ? err.message : "Failed to remove member");
188
119
  }
189
120
  }
190
- return /* @__PURE__ */ jsxs3("section", { className: "flex flex-col gap-4", children: [
191
- /* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between", children: [
192
- /* @__PURE__ */ jsx3("h4", { className: "text-sm font-semibold text-[var(--text-primary)]", children: "Members" }),
193
- /* @__PURE__ */ jsxs3("span", { className: "text-sm text-[var(--text-muted)]", children: [
121
+ return /* @__PURE__ */ jsxs2("section", { className: "@container/members flex flex-col gap-4", children: [
122
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between", children: [
123
+ /* @__PURE__ */ jsx2(Heading, { as: "h4", variant: "subsection", children: "Members" }),
124
+ /* @__PURE__ */ jsxs2("span", { className: "text-sm text-[var(--text-muted)]", children: [
194
125
  members.length,
195
126
  " total"
196
127
  ] })
197
128
  ] }),
198
- /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-2", children: [
199
- members.map((member) => /* @__PURE__ */ jsx3(
129
+ /* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-2", children: [
130
+ members.map((member) => /* @__PURE__ */ jsx2(
200
131
  MemberRow,
201
132
  {
202
133
  member,
203
- canManage,
134
+ editable: canManageMember(member, currentRole, currentUserId),
135
+ self: isCurrentMember(member, currentUserId),
204
136
  onChangeRole: changeRole,
205
137
  onRemove: remove
206
138
  },
207
139
  member.id
208
140
  )),
209
- members.length === 0 && /* @__PURE__ */ jsx3("p", { className: "text-sm text-[var(--text-muted)]", children: "No team members yet." })
141
+ members.length === 0 && /* @__PURE__ */ jsx2("p", { className: "text-sm text-[var(--text-muted)]", children: "No team members yet." })
210
142
  ] }),
211
- canManage && showInviteForm && /* @__PURE__ */ jsx3("div", { className: "border-t border-[var(--border-default)] pt-3", children: /* @__PURE__ */ jsx3(InviteForm, { label: "Invite member", placeholder: "Email address", onInvite: invite }) })
143
+ canManage && showInviteForm && /* @__PURE__ */ jsx2("div", { className: "border-t border-[var(--border-default)] pt-3", children: /* @__PURE__ */ jsx2(InviteForm, { label: "Invite member", placeholder: "Email address", onInvite: invite }) })
212
144
  ] });
213
145
  }
214
- function MemberRow({ member, canManage, onChangeRole, onRemove }) {
146
+ function MemberRow({ member, editable, self, onChangeRole, onRemove }) {
215
147
  const pending = member.acceptedAt == null;
216
148
  const label = member.name ?? member.email ?? "Unknown";
217
149
  const initial = (member.name?.[0] ?? member.email?.[0] ?? "?").toUpperCase();
218
- const isOwner = member.role === "owner";
219
- const editable = canManage && !isOwner && !member.inherited;
220
- return /* @__PURE__ */ jsxs3("div", { className: "flex flex-wrap items-center justify-between gap-x-3 gap-y-2 border-b border-[var(--border-default)] py-2 last:border-0", children: [
221
- /* @__PURE__ */ jsxs3("div", { className: "flex min-w-0 flex-1 items-center gap-3", children: [
222
- /* @__PURE__ */ jsx3("div", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[var(--bg-input)] text-sm font-semibold text-[var(--text-secondary)]", children: initial }),
223
- /* @__PURE__ */ jsxs3("div", { className: "min-w-0", children: [
224
- /* @__PURE__ */ jsxs3("p", { className: "flex flex-wrap items-center gap-x-2 text-sm font-medium text-[var(--text-primary)]", children: [
225
- /* @__PURE__ */ jsx3("span", { className: "min-w-0 [overflow-wrap:anywhere]", children: label }),
226
- pending && /* @__PURE__ */ jsx3("span", { className: "rounded border border-[var(--border-default)] px-1.5 py-0.5 text-sm font-medium text-[var(--text-muted)]", children: "Pending" })
150
+ return /* @__PURE__ */ jsxs2("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-2 border-b border-[var(--border-default)] py-2 last:border-0", children: [
151
+ /* @__PURE__ */ jsxs2("div", { className: "flex min-w-0 flex-1 items-center gap-3 @max-[36rem]/members:basis-40", children: [
152
+ /* @__PURE__ */ jsx2("div", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[var(--bg-input)] text-sm font-semibold text-[var(--text-secondary)]", children: initial }),
153
+ /* @__PURE__ */ jsxs2("div", { className: "min-w-0", children: [
154
+ /* @__PURE__ */ jsxs2("p", { className: "flex flex-wrap items-center gap-x-2 text-sm font-medium text-[var(--text-primary)]", children: [
155
+ /* @__PURE__ */ jsxs2("span", { className: "min-w-0 [overflow-wrap:anywhere]", children: [
156
+ label,
157
+ self && /* @__PURE__ */ jsx2("span", { className: "font-normal text-[var(--text-muted)]", children: " (you)" })
158
+ ] }),
159
+ pending && /* @__PURE__ */ jsx2("span", { className: "rounded border border-[var(--border-default)] px-1.5 py-0.5 text-sm font-medium text-[var(--text-muted)]", children: "Pending" })
227
160
  ] }),
228
- member.name && member.email && /* @__PURE__ */ jsx3("p", { className: "text-sm text-[var(--text-muted)] [overflow-wrap:anywhere]", children: member.email })
161
+ member.name && member.email && /* @__PURE__ */ jsx2("p", { className: "text-sm text-[var(--text-muted)] [overflow-wrap:anywhere]", children: member.email })
229
162
  ] })
230
163
  ] }),
231
- /* @__PURE__ */ jsx3("div", { className: "ml-auto flex shrink-0 items-center gap-2", children: isOwner ? /* @__PURE__ */ jsx3("span", { className: "rounded border border-[var(--border-default)] px-2 py-0.5 text-sm text-[var(--text-secondary)]", children: member.inherited ? "Org Admin" : "Owner" }) : editable ? /* @__PURE__ */ jsxs3(Fragment, { children: [
232
- /* @__PURE__ */ jsx3(
164
+ editable ? /* @__PURE__ */ jsxs2(Fragment, { children: [
165
+ /* @__PURE__ */ jsx2("div", { className: "w-72 shrink-0 @max-[36rem]/members:order-last @max-[36rem]/members:w-full", children: /* @__PURE__ */ jsx2(
233
166
  RoleSelect,
234
167
  {
235
168
  value: member.role,
236
169
  ariaLabel: `Role for ${label}`,
237
170
  onChange: (role) => onChangeRole(member.id, role)
238
171
  }
239
- ),
240
- /* @__PURE__ */ jsx3(
241
- "button",
242
- {
243
- type: "button",
244
- "aria-label": `Remove ${label}`,
245
- onClick: () => onRemove(member.id),
246
- className: "rounded px-2 py-1 text-sm text-[var(--text-danger)] hover:bg-[var(--border-default)]",
247
- children: "Remove"
248
- }
249
- )
250
- ] }) : /* @__PURE__ */ jsx3("span", { className: "rounded border border-[var(--border-default)] px-2 py-0.5 text-sm capitalize text-[var(--text-secondary)]", children: member.role }) })
172
+ ) }),
173
+ /* @__PURE__ */ jsx2(Button2, { type: "button", variant: "destructive-outline", "aria-label": `Remove ${label}`, onClick: () => onRemove(member.id), children: "Remove" })
174
+ ] }) : /* @__PURE__ */ jsx2("span", { className: "ml-auto rounded border border-[var(--border-default)] px-2 py-0.5 text-sm text-[var(--text-secondary)]", children: memberRoleLabel(member) })
251
175
  ] });
252
176
  }
253
177
 
254
178
  // src/teams-react/components/InvitationsPanel.tsx
255
- import { useState as useState3 } from "react";
256
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
179
+ import { Button as Button3, Heading as Heading2 } from "@tangle-network/ui/primitives";
180
+ import { useState as useState2 } from "react";
181
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
257
182
  var BADGE_BASE = "inline-flex items-center rounded border px-2 py-0.5 text-sm font-medium";
258
183
  var STATUS = {
259
184
  accepted: { label: "Joined", tone: "border-[var(--surface-success-border)] bg-[var(--surface-success-bg)] text-[var(--surface-success-text)]" },
@@ -261,27 +186,6 @@ var STATUS = {
261
186
  revoked: { label: "Revoked", tone: "border-[var(--surface-danger-border)] bg-[var(--surface-danger-bg)] text-[var(--surface-danger-text)]" },
262
187
  expired: { label: "Expired", tone: "border-[var(--border-default)] text-[var(--text-muted)]" }
263
188
  };
264
- function capitalize(text) {
265
- return text.length === 0 ? text : text[0].toUpperCase() + text.slice(1);
266
- }
267
- function shortDate(value) {
268
- return new Date(value).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
269
- }
270
- function invitationDetail(invitation) {
271
- const role = capitalize(invitation.permissions);
272
- switch (invitation.status) {
273
- case "accepted":
274
- return invitation.acceptedAt ? `${role} \xB7 joined ${shortDate(invitation.acceptedAt)}` : role;
275
- case "pending": {
276
- const delivery = invitation.emailStatus === "sent" ? "email sent" : invitation.emailStatus === "not_sent" ? "email not sent yet" : null;
277
- return [role, delivery, `expires ${shortDate(invitation.expiresAt)}`].filter(Boolean).join(" \xB7 ");
278
- }
279
- case "expired":
280
- return `${role} \xB7 expired ${shortDate(invitation.expiresAt)}`;
281
- case "revoked":
282
- return role;
283
- }
284
- }
285
189
  function InvitationsPanel({
286
190
  invitations,
287
191
  currentRole,
@@ -291,7 +195,7 @@ function InvitationsPanel({
291
195
  onCopy,
292
196
  onNotice
293
197
  }) {
294
- const [busyId, setBusyId] = useState3(null);
198
+ const [busyId, setBusyId] = useState2(null);
295
199
  const canManage = hasWorkspaceRole(currentRole, "admin");
296
200
  function notify(kind, message) {
297
201
  onNotice?.({ kind, message });
@@ -337,17 +241,17 @@ function InvitationsPanel({
337
241
  setBusyId(null);
338
242
  }
339
243
  }
340
- return /* @__PURE__ */ jsxs4("section", { className: "flex flex-col gap-4", children: [
341
- canManage && /* @__PURE__ */ jsx4(InviteForm, { label: "Invite by email", placeholder: "colleague@example.com", onInvite: invite }),
342
- /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-2 border-t border-[var(--border-default)] pt-3", children: [
343
- /* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-between", children: [
344
- /* @__PURE__ */ jsx4("h4", { className: "text-sm font-semibold text-[var(--text-primary)]", children: "Invitation history" }),
345
- /* @__PURE__ */ jsxs4("span", { className: "text-sm text-[var(--text-muted)]", children: [
244
+ return /* @__PURE__ */ jsxs3("section", { className: "flex flex-col gap-4", children: [
245
+ canManage && /* @__PURE__ */ jsx3(InviteForm, { label: "Invite by email", placeholder: "colleague@example.com", onInvite: invite }),
246
+ /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-2 border-t border-[var(--border-default)] pt-3", children: [
247
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between", children: [
248
+ /* @__PURE__ */ jsx3(Heading2, { as: "h4", variant: "subsection", children: "Invitation history" }),
249
+ /* @__PURE__ */ jsxs3("span", { className: "text-sm text-[var(--text-muted)]", children: [
346
250
  invitations.length,
347
251
  " total"
348
252
  ] })
349
253
  ] }),
350
- invitations.length === 0 ? /* @__PURE__ */ jsx4("p", { className: "text-sm text-[var(--text-muted)]", children: "No invitations yet." }) : invitations.map((invitation) => /* @__PURE__ */ jsx4(
254
+ invitations.length === 0 ? /* @__PURE__ */ jsx3("p", { className: "text-sm text-[var(--text-muted)]", children: "No invitations yet." }) : invitations.map((invitation) => /* @__PURE__ */ jsx3(
351
255
  InvitationRow,
352
256
  {
353
257
  invitation,
@@ -365,58 +269,31 @@ function InvitationsPanel({
365
269
  function InvitationRow({ invitation, canManage, busy, onCopy, onResend, onRevoke }) {
366
270
  const isPending = invitation.status === "pending";
367
271
  const status = STATUS[invitation.status];
368
- return /* @__PURE__ */ jsxs4("div", { className: "rounded-lg border border-[var(--border-default)] bg-[var(--bg-input)] p-3", children: [
369
- /* @__PURE__ */ jsxs4("div", { className: "flex items-start justify-between gap-3", children: [
370
- /* @__PURE__ */ jsxs4("div", { className: "min-w-0", children: [
371
- /* @__PURE__ */ jsx4("p", { className: "truncate text-sm font-medium text-[var(--text-primary)]", title: invitation.email, children: invitation.email }),
372
- /* @__PURE__ */ jsx4("p", { className: "text-sm text-[var(--text-muted)]", children: invitationDetail(invitation) }),
373
- isPending && invitation.emailStatus === "failed" && /* @__PURE__ */ jsx4("p", { className: "mt-1 text-sm text-[var(--surface-danger-text)]", children: "The email was not delivered. Copy the link and send it yourself." })
272
+ return /* @__PURE__ */ jsxs3("div", { className: "rounded-lg border border-[var(--border-default)] bg-[var(--bg-input)] p-3", children: [
273
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-start justify-between gap-3", children: [
274
+ /* @__PURE__ */ jsxs3("div", { className: "min-w-0", children: [
275
+ /* @__PURE__ */ jsx3("p", { className: "truncate text-sm font-medium text-[var(--text-primary)]", title: invitation.email, children: invitation.email }),
276
+ /* @__PURE__ */ jsx3("p", { className: "text-sm text-[var(--text-muted)]", children: invitationDetail(invitation) }),
277
+ isPending && invitation.emailStatus === "failed" && /* @__PURE__ */ jsx3("p", { className: "mt-1 text-sm text-[var(--surface-danger-text)]", children: "The email was not delivered. Copy the link and send it yourself." })
374
278
  ] }),
375
- /* @__PURE__ */ jsx4("span", { className: `${BADGE_BASE} ${status.tone} shrink-0`, children: status.label })
279
+ /* @__PURE__ */ jsx3("span", { className: `${BADGE_BASE} ${status.tone} shrink-0`, children: status.label })
376
280
  ] }),
377
- canManage && isPending && /* @__PURE__ */ jsxs4("div", { className: "mt-3 flex flex-wrap items-center justify-end gap-2", children: [
378
- /* @__PURE__ */ jsx4(
379
- "button",
380
- {
381
- type: "button",
382
- onClick: onCopy,
383
- disabled: busy,
384
- className: "rounded border border-[var(--border-default)] px-2.5 py-1 text-sm text-[var(--text-secondary)] hover:text-[var(--text-primary)] disabled:opacity-50",
385
- children: "Copy link"
386
- }
387
- ),
388
- /* @__PURE__ */ jsx4(
389
- "button",
390
- {
391
- type: "button",
392
- onClick: onResend,
393
- disabled: busy,
394
- className: "rounded border border-[var(--border-default)] px-2.5 py-1 text-sm text-[var(--text-secondary)] hover:text-[var(--text-primary)] disabled:opacity-50",
395
- children: busy ? "Working\u2026" : "Resend"
396
- }
397
- ),
398
- /* @__PURE__ */ jsx4(
399
- "button",
400
- {
401
- type: "button",
402
- onClick: onRevoke,
403
- disabled: busy,
404
- className: "rounded px-2.5 py-1 text-sm text-[var(--text-danger)] hover:bg-[var(--border-default)] disabled:opacity-50",
405
- children: "Revoke"
406
- }
407
- )
281
+ canManage && isPending && /* @__PURE__ */ jsxs3("div", { className: "mt-3 flex flex-wrap items-center justify-end gap-2", children: [
282
+ /* @__PURE__ */ jsx3(Button3, { type: "button", variant: "outline", onClick: onCopy, disabled: busy, children: "Copy link" }),
283
+ /* @__PURE__ */ jsx3(Button3, { type: "button", variant: "outline", onClick: onResend, disabled: busy, children: busy ? "Working\u2026" : "Resend" }),
284
+ /* @__PURE__ */ jsx3(Button3, { type: "button", variant: "destructive-outline", onClick: onRevoke, disabled: busy, children: "Revoke" })
408
285
  ] })
409
286
  ] });
410
287
  }
411
288
 
412
289
  // src/teams-react/components/InviteAcceptPage.tsx
413
- import { useState as useState4 } from "react";
290
+ import { useState as useState3 } from "react";
414
291
 
415
292
  // src/teams-react/components/BrandMark.tsx
416
293
  import { lazy, Suspense } from "react";
417
- import { jsx as jsx5 } from "react/jsx-runtime";
294
+ import { jsx as jsx4 } from "react/jsx-runtime";
418
295
  function MarkSpacer({ size }) {
419
- return /* @__PURE__ */ jsx5("span", { "aria-hidden": true, style: { display: "inline-block", width: size, height: size } });
296
+ return /* @__PURE__ */ jsx4("span", { "aria-hidden": true, style: { display: "inline-block", width: size, height: size } });
420
297
  }
421
298
  var LazyKnot = lazy(async () => {
422
299
  try {
@@ -427,57 +304,57 @@ var LazyKnot = lazy(async () => {
427
304
  }
428
305
  });
429
306
  function BrandMark({ size, className }) {
430
- return /* @__PURE__ */ jsx5(Suspense, { fallback: /* @__PURE__ */ jsx5(MarkSpacer, { size }), children: /* @__PURE__ */ jsx5(LazyKnot, { size, className }) });
307
+ return /* @__PURE__ */ jsx4(Suspense, { fallback: /* @__PURE__ */ jsx4(MarkSpacer, { size }), children: /* @__PURE__ */ jsx4(LazyKnot, { size, className }) });
431
308
  }
432
309
 
433
310
  // src/teams-react/components/InviteAcceptPage.tsx
434
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
311
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
435
312
  function InviteAcceptPage({ details, onAccept, onNavigate, onResendVerification }) {
436
- const [accepting, setAccepting] = useState4(false);
437
- const [acceptError, setAcceptError] = useState4(null);
438
- const [accepted, setAccepted] = useState4(false);
439
- const [resendingVerification, setResendingVerification] = useState4(false);
440
- const [verificationSent, setVerificationSent] = useState4(false);
313
+ const [accepting, setAccepting] = useState3(false);
314
+ const [acceptError, setAcceptError] = useState3(null);
315
+ const [accepted, setAccepted] = useState3(false);
316
+ const [resendingVerification, setResendingVerification] = useState3(false);
317
+ const [verificationSent, setVerificationSent] = useState3(false);
441
318
  if (details.status === "invalid") {
442
- return /* @__PURE__ */ jsx6(Shell, { title: "Invalid invitation", body: "This link is invalid or already used. Ask the workspace admin to send a new one.", children: /* @__PURE__ */ jsx6(PrimaryButton, { onClick: () => onNavigate({ kind: "sign-in" }), children: "Go to sign in" }) });
319
+ return /* @__PURE__ */ jsx5(Shell, { title: "Invalid invitation", body: "This link is invalid or already used. Ask the workspace admin to send a new one.", children: /* @__PURE__ */ jsx5(PrimaryButton, { onClick: () => onNavigate({ kind: "sign-in" }), children: "Go to sign in" }) });
443
320
  }
444
321
  if (details.status === "already-accepted") {
445
- return /* @__PURE__ */ jsx6(Shell, { title: "Already accepted", body: "This invitation has already been accepted.", children: /* @__PURE__ */ jsx6(PrimaryButton, { onClick: () => onNavigate({ kind: "open-app" }), children: "Open workspace" }) });
322
+ return /* @__PURE__ */ jsx5(Shell, { title: "Already accepted", body: "This invitation has already been accepted.", children: /* @__PURE__ */ jsx5(PrimaryButton, { onClick: () => onNavigate({ kind: "open-app" }), children: "Open workspace" }) });
446
323
  }
447
324
  if (details.status === "expired") {
448
- return /* @__PURE__ */ jsx6(Shell, { title: "Invitation expired", body: "This invitation has expired. Ask the workspace admin to send a new one.", children: /* @__PURE__ */ jsx6(PrimaryButton, { onClick: () => onNavigate({ kind: "sign-in" }), children: "Go to sign in" }) });
325
+ return /* @__PURE__ */ jsx5(Shell, { title: "Invitation expired", body: "This invitation has expired. Ask the workspace admin to send a new one.", children: /* @__PURE__ */ jsx5(PrimaryButton, { onClick: () => onNavigate({ kind: "sign-in" }), children: "Go to sign in" }) });
449
326
  }
450
327
  if (details.status === "revoked") {
451
- return /* @__PURE__ */ jsx6(Shell, { title: "Invitation revoked", body: "This invitation has been revoked. Ask the workspace admin to send a new one.", children: /* @__PURE__ */ jsx6(PrimaryButton, { onClick: () => onNavigate({ kind: "sign-in" }), children: "Go to sign in" }) });
328
+ return /* @__PURE__ */ jsx5(Shell, { title: "Invitation revoked", body: "This invitation has been revoked. Ask the workspace admin to send a new one.", children: /* @__PURE__ */ jsx5(PrimaryButton, { onClick: () => onNavigate({ kind: "sign-in" }), children: "Go to sign in" }) });
452
329
  }
453
330
  const workspaceName = details.workspaceName ?? "a workspace";
454
331
  const inviterPrefix = details.inviterName ? `${details.inviterName} invited you` : "You've been invited";
455
332
  const roleSuffix = details.role ? ` as ${details.role}` : "";
456
333
  if (!details.currentUserEmail) {
457
- return /* @__PURE__ */ jsxs5(Shell, { title: "You've been invited", children: [
458
- /* @__PURE__ */ jsxs5("p", { className: "mb-4 text-sm text-[var(--text-secondary)]", children: [
334
+ return /* @__PURE__ */ jsxs4(Shell, { title: "You've been invited", children: [
335
+ /* @__PURE__ */ jsxs4("p", { className: "mb-4 text-sm text-[var(--text-secondary)]", children: [
459
336
  inviterPrefix,
460
337
  " to join ",
461
- /* @__PURE__ */ jsx6("span", { className: "font-medium text-[var(--text-primary)]", children: workspaceName }),
338
+ /* @__PURE__ */ jsx5("span", { className: "font-medium text-[var(--text-primary)]", children: workspaceName }),
462
339
  roleSuffix,
463
340
  "."
464
341
  ] }),
465
- details.inviteEmail && /* @__PURE__ */ jsxs5("p", { className: "mb-6 text-sm text-[var(--text-secondary)]", children: [
342
+ details.inviteEmail && /* @__PURE__ */ jsxs4("p", { className: "mb-6 text-sm text-[var(--text-secondary)]", children: [
466
343
  "Sign in or create an account with",
467
344
  " ",
468
- /* @__PURE__ */ jsx6("span", { className: "font-medium text-[var(--text-primary)]", children: details.inviteEmail }),
345
+ /* @__PURE__ */ jsx5("span", { className: "font-medium text-[var(--text-primary)]", children: details.inviteEmail }),
469
346
  " to accept."
470
347
  ] }),
471
- /* @__PURE__ */ jsxs5("div", { className: "flex gap-2", children: [
472
- /* @__PURE__ */ jsx6(PrimaryButton, { onClick: () => onNavigate({ kind: "sign-in" }), children: "Sign in" }),
473
- /* @__PURE__ */ jsx6(SecondaryButton, { onClick: () => onNavigate({ kind: "sign-up" }), children: "Create account" })
348
+ /* @__PURE__ */ jsxs4("div", { className: "flex gap-2", children: [
349
+ /* @__PURE__ */ jsx5(PrimaryButton, { onClick: () => onNavigate({ kind: "sign-in" }), children: "Sign in" }),
350
+ /* @__PURE__ */ jsx5(SecondaryButton, { onClick: () => onNavigate({ kind: "sign-up" }), children: "Create account" })
474
351
  ] })
475
352
  ] });
476
353
  }
477
354
  if (accepted) {
478
- return /* @__PURE__ */ jsx6(Shell, { title: "Welcome!", children: /* @__PURE__ */ jsxs5("p", { className: "text-sm text-[var(--text-secondary)]", children: [
355
+ return /* @__PURE__ */ jsx5(Shell, { title: "Welcome!", children: /* @__PURE__ */ jsxs4("p", { className: "text-sm text-[var(--text-secondary)]", children: [
479
356
  "You've joined ",
480
- /* @__PURE__ */ jsx6("span", { className: "font-medium text-[var(--text-primary)]", children: workspaceName }),
357
+ /* @__PURE__ */ jsx5("span", { className: "font-medium text-[var(--text-primary)]", children: workspaceName }),
481
358
  "."
482
359
  ] }) });
483
360
  }
@@ -511,36 +388,36 @@ function InviteAcceptPage({ details, onAccept, onNavigate, onResendVerification
511
388
  setResendingVerification(false);
512
389
  }
513
390
  }
514
- return /* @__PURE__ */ jsxs5(Shell, { title: "Join workspace", children: [
515
- /* @__PURE__ */ jsxs5("p", { className: "mb-4 text-sm text-[var(--text-secondary)]", children: [
391
+ return /* @__PURE__ */ jsxs4(Shell, { title: "Join workspace", children: [
392
+ /* @__PURE__ */ jsxs4("p", { className: "mb-4 text-sm text-[var(--text-secondary)]", children: [
516
393
  inviterPrefix,
517
394
  " to join ",
518
- /* @__PURE__ */ jsx6("span", { className: "font-medium text-[var(--text-primary)]", children: workspaceName }),
395
+ /* @__PURE__ */ jsx5("span", { className: "font-medium text-[var(--text-primary)]", children: workspaceName }),
519
396
  roleSuffix,
520
397
  "."
521
398
  ] }),
522
- /* @__PURE__ */ jsxs5("p", { className: "mb-4 text-sm text-[var(--text-secondary)]", children: [
399
+ /* @__PURE__ */ jsxs4("p", { className: "mb-4 text-sm text-[var(--text-secondary)]", children: [
523
400
  "Signed in as ",
524
- /* @__PURE__ */ jsx6("span", { className: "font-medium text-[var(--text-primary)]", children: details.currentUserEmail })
401
+ /* @__PURE__ */ jsx5("span", { className: "font-medium text-[var(--text-primary)]", children: details.currentUserEmail })
525
402
  ] }),
526
- expiryLabel && /* @__PURE__ */ jsxs5("p", { className: "mb-4 text-xs text-[var(--text-muted)]", children: [
403
+ expiryLabel && /* @__PURE__ */ jsxs4("p", { className: "mb-4 text-xs text-[var(--text-muted)]", children: [
527
404
  "This invitation expires on ",
528
405
  expiryLabel,
529
406
  "."
530
407
  ] }),
531
- emailMismatch && /* @__PURE__ */ jsxs5(
408
+ emailMismatch && /* @__PURE__ */ jsxs4(
532
409
  "div",
533
410
  {
534
411
  role: "alert",
535
412
  className: "mb-4 rounded-md border border-[var(--border-default)] px-4 py-2 text-sm text-[var(--text-warning)]",
536
413
  children: [
537
414
  "This invitation was sent to ",
538
- /* @__PURE__ */ jsx6("span", { className: "font-medium", children: details.inviteEmail }),
415
+ /* @__PURE__ */ jsx5("span", { className: "font-medium", children: details.inviteEmail }),
539
416
  ". Switch to that account to accept it."
540
417
  ]
541
418
  }
542
419
  ),
543
- needsVerification && /* @__PURE__ */ jsx6(
420
+ needsVerification && /* @__PURE__ */ jsx5(
544
421
  "div",
545
422
  {
546
423
  role: "alert",
@@ -548,33 +425,33 @@ function InviteAcceptPage({ details, onAccept, onNavigate, onResendVerification
548
425
  children: verificationSent ? "Verification email sent \u2014 check your inbox, then refresh this page to accept." : "Verify your email address before you can accept this invitation."
549
426
  }
550
427
  ),
551
- acceptError && /* @__PURE__ */ jsx6("p", { role: "alert", className: "mb-4 text-sm text-[var(--text-danger)]", children: acceptError }),
552
- /* @__PURE__ */ jsxs5("div", { className: "flex gap-2", children: [
553
- emailMismatch ? /* @__PURE__ */ jsx6(PrimaryButton, { onClick: () => onNavigate({ kind: "switch-account" }), children: "Switch account" }) : needsVerification ? onResendVerification ? /* @__PURE__ */ jsx6(
428
+ acceptError && /* @__PURE__ */ jsx5("p", { role: "alert", className: "mb-4 text-sm text-[var(--text-danger)]", children: acceptError }),
429
+ /* @__PURE__ */ jsxs4("div", { className: "flex gap-2", children: [
430
+ emailMismatch ? /* @__PURE__ */ jsx5(PrimaryButton, { onClick: () => onNavigate({ kind: "switch-account" }), children: "Switch account" }) : needsVerification ? onResendVerification ? /* @__PURE__ */ jsx5(
554
431
  PrimaryButton,
555
432
  {
556
433
  onClick: () => void handleResendVerification(),
557
434
  disabled: resendingVerification || verificationSent,
558
435
  children: verificationSent ? "Verification sent" : resendingVerification ? "Sending\u2026" : "Resend verification email"
559
436
  }
560
- ) : /* @__PURE__ */ jsx6(PrimaryButton, { onClick: () => onNavigate({ kind: "open-app" }), children: "Open app" }) : /* @__PURE__ */ jsx6(PrimaryButton, { onClick: () => void handleAccept(), disabled: accepting, children: accepting ? "Accepting\u2026" : "Accept invitation" }),
561
- /* @__PURE__ */ jsx6(SecondaryButton, { onClick: () => onNavigate({ kind: "open-app" }), children: "Not now" })
437
+ ) : /* @__PURE__ */ jsx5(PrimaryButton, { onClick: () => onNavigate({ kind: "open-app" }), children: "Open app" }) : /* @__PURE__ */ jsx5(PrimaryButton, { onClick: () => void handleAccept(), disabled: accepting, children: accepting ? "Accepting\u2026" : "Accept invitation" }),
438
+ /* @__PURE__ */ jsx5(SecondaryButton, { onClick: () => onNavigate({ kind: "open-app" }), children: "Not now" })
562
439
  ] })
563
440
  ] });
564
441
  }
565
442
  function Shell({ title, body, children }) {
566
- return /* @__PURE__ */ jsxs5("div", { className: "mx-auto flex w-full max-w-sm flex-col", children: [
567
- /* @__PURE__ */ jsxs5("span", { className: "mb-4 flex items-center gap-2 text-[var(--text-muted)]", children: [
568
- /* @__PURE__ */ jsx6(BrandMark, { size: 22, className: "shrink-0" }),
569
- /* @__PURE__ */ jsx6("span", { className: "text-xs font-semibold uppercase tracking-[0.05em]", children: "Tangle Teams" })
443
+ return /* @__PURE__ */ jsxs4("div", { className: "mx-auto flex w-full max-w-sm flex-col", children: [
444
+ /* @__PURE__ */ jsxs4("span", { className: "mb-4 flex items-center gap-2 text-[var(--text-muted)]", children: [
445
+ /* @__PURE__ */ jsx5(BrandMark, { size: 22, className: "shrink-0" }),
446
+ /* @__PURE__ */ jsx5("span", { className: "text-xs font-semibold uppercase tracking-[0.05em]", children: "Tangle Teams" })
570
447
  ] }),
571
- /* @__PURE__ */ jsx6("h1", { className: "mb-1 text-xl font-semibold tracking-tight text-[var(--text-primary)]", children: title }),
572
- body && /* @__PURE__ */ jsx6("p", { className: "mb-6 text-sm text-[var(--text-secondary)]", children: body }),
448
+ /* @__PURE__ */ jsx5("h1", { className: "mb-1 text-xl font-semibold tracking-tight text-[var(--text-primary)]", children: title }),
449
+ body && /* @__PURE__ */ jsx5("p", { className: "mb-6 text-sm text-[var(--text-secondary)]", children: body }),
573
450
  children
574
451
  ] });
575
452
  }
576
453
  function PrimaryButton({ children, onClick, disabled }) {
577
- return /* @__PURE__ */ jsx6(
454
+ return /* @__PURE__ */ jsx5(
578
455
  "button",
579
456
  {
580
457
  type: "button",
@@ -586,7 +463,7 @@ function PrimaryButton({ children, onClick, disabled }) {
586
463
  );
587
464
  }
588
465
  function SecondaryButton({ children, onClick }) {
589
- return /* @__PURE__ */ jsx6(
466
+ return /* @__PURE__ */ jsx5(
590
467
  "button",
591
468
  {
592
469
  type: "button",