@tangle-network/agent-app 0.28.0 → 0.30.0

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 (47) hide show
  1. package/dist/{DesignCanvasEditor-TN7CWUJW.js → DesignCanvasEditor-QQNAZRJQ.js} +3 -3
  2. package/dist/InviteAcceptPage-EUOGZO2X.js +7 -0
  3. package/dist/InviteAcceptPage-EUOGZO2X.js.map +1 -0
  4. package/dist/MembersPanel-4CGUQRPF.js +8 -0
  5. package/dist/MembersPanel-4CGUQRPF.js.map +1 -0
  6. package/dist/access-ChAHG4Tp.d.ts +539 -0
  7. package/dist/chunk-3G334FVA.js +162 -0
  8. package/dist/chunk-3G334FVA.js.map +1 -0
  9. package/dist/chunk-3SVAA3MA.js +41 -0
  10. package/dist/chunk-3SVAA3MA.js.map +1 -0
  11. package/dist/chunk-535V6BH6.js +128 -0
  12. package/dist/chunk-535V6BH6.js.map +1 -0
  13. package/dist/chunk-63CE7FEZ.js +63 -0
  14. package/dist/chunk-63CE7FEZ.js.map +1 -0
  15. package/dist/{chunk-Y2SZVW35.js → chunk-LVD37K5V.js} +41 -41
  16. package/dist/chunk-LVD37K5V.js.map +1 -0
  17. package/dist/{chunk-J7JNV5BH.js → chunk-WOVCWPFF.js} +2 -2
  18. package/dist/{chunk-7I37CO5D.js → chunk-YYYXGLLQ.js} +4 -4
  19. package/dist/design-canvas-react/index.js +3 -3
  20. package/dist/design-canvas-react/lazy.js +1 -1
  21. package/dist/index.js +7 -7
  22. package/dist/roles-CLtYKHHl.d.ts +49 -0
  23. package/dist/teams/drizzle.d.ts +42 -0
  24. package/dist/teams/drizzle.js +194 -0
  25. package/dist/teams/drizzle.js.map +1 -0
  26. package/dist/teams/index.d.ts +44 -0
  27. package/dist/teams/index.js +39 -0
  28. package/dist/teams/index.js.map +1 -0
  29. package/dist/teams/members-api.d.ts +152 -0
  30. package/dist/teams/members-api.js +261 -0
  31. package/dist/teams/members-api.js.map +1 -0
  32. package/dist/teams-react/index.d.ts +81 -0
  33. package/dist/teams-react/index.js +12 -0
  34. package/dist/teams-react/index.js.map +1 -0
  35. package/dist/teams-react/lazy.d.ts +9 -0
  36. package/dist/teams-react/lazy.js +13 -0
  37. package/dist/teams-react/lazy.js.map +1 -0
  38. package/dist/theme/index.js +1 -1
  39. package/dist/theme/tokens.css +50 -48
  40. package/dist/web-react/index.d.ts +6 -1
  41. package/dist/web-react/index.js +8 -14
  42. package/dist/web-react/index.js.map +1 -1
  43. package/package.json +26 -1
  44. package/dist/chunk-Y2SZVW35.js.map +0 -1
  45. /package/dist/{DesignCanvasEditor-TN7CWUJW.js.map → DesignCanvasEditor-QQNAZRJQ.js.map} +0 -0
  46. /package/dist/{chunk-J7JNV5BH.js.map → chunk-WOVCWPFF.js.map} +0 -0
  47. /package/dist/{chunk-7I37CO5D.js.map → chunk-YYYXGLLQ.js.map} +0 -0
@@ -0,0 +1,162 @@
1
+ import {
2
+ hasWorkspaceRole
3
+ } from "./chunk-63CE7FEZ.js";
4
+
5
+ // src/teams-react/components/MembersPanel.tsx
6
+ import { useState } from "react";
7
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
+ var ASSIGNABLE = [
9
+ { value: "viewer", label: "Viewer" },
10
+ { value: "editor", label: "Editor" },
11
+ { value: "admin", label: "Admin" }
12
+ ];
13
+ function MembersPanel({
14
+ members,
15
+ currentRole,
16
+ onInvite,
17
+ onChangeRole,
18
+ onRemove,
19
+ onNotice
20
+ }) {
21
+ const [inviteEmail, setInviteEmail] = useState("");
22
+ const [inviteRole, setInviteRole] = useState("editor");
23
+ const [inviting, setInviting] = useState(false);
24
+ const canManage = hasWorkspaceRole(currentRole, "admin");
25
+ function notify(kind, message) {
26
+ onNotice?.({ kind, message });
27
+ }
28
+ async function submitInvite() {
29
+ const email = inviteEmail.trim();
30
+ if (!email || inviting) return;
31
+ setInviting(true);
32
+ try {
33
+ const result = await onInvite({ email, role: inviteRole });
34
+ if (result && "inviteUrl" in result && result.inviteUrl) {
35
+ notify("success", `Invite link ready for ${email}`);
36
+ } else {
37
+ notify("success", `Invited ${email}`);
38
+ }
39
+ setInviteEmail("");
40
+ } catch (err) {
41
+ notify("error", err instanceof Error ? err.message : "Failed to invite");
42
+ } finally {
43
+ setInviting(false);
44
+ }
45
+ }
46
+ async function changeRole(memberId, role) {
47
+ try {
48
+ await onChangeRole({ memberId, role });
49
+ notify("success", "Role updated");
50
+ } catch (err) {
51
+ notify("error", err instanceof Error ? err.message : "Failed to update role");
52
+ }
53
+ }
54
+ async function remove(memberId) {
55
+ try {
56
+ await onRemove({ memberId });
57
+ notify("success", "Member removed");
58
+ } catch (err) {
59
+ notify("error", err instanceof Error ? err.message : "Failed to remove member");
60
+ }
61
+ }
62
+ return /* @__PURE__ */ jsxs("section", { className: "flex flex-col gap-4", children: [
63
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
64
+ members.map((member) => /* @__PURE__ */ jsx(
65
+ MemberRow,
66
+ {
67
+ member,
68
+ canManage,
69
+ onChangeRole: changeRole,
70
+ onRemove: remove
71
+ },
72
+ member.id
73
+ )),
74
+ members.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-sm text-[var(--text-muted)]", children: "No team members yet." })
75
+ ] }),
76
+ canManage && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5 border-t border-[var(--border-default)] pt-3", children: [
77
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-[var(--text-muted)]", children: "Invite member" }),
78
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
79
+ /* @__PURE__ */ jsx(
80
+ "input",
81
+ {
82
+ type: "email",
83
+ placeholder: "Email address",
84
+ value: inviteEmail,
85
+ "aria-label": "Invite email address",
86
+ onChange: (event) => setInviteEmail(event.target.value),
87
+ onKeyDown: (event) => {
88
+ if (event.key === "Enter") void submitInvite();
89
+ },
90
+ className: "flex-1 rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-3 py-1.5 text-sm text-[var(--text-primary)] placeholder:text-[var(--text-muted)]"
91
+ }
92
+ ),
93
+ /* @__PURE__ */ jsx(
94
+ "select",
95
+ {
96
+ value: inviteRole,
97
+ "aria-label": "Invite role",
98
+ onChange: (event) => setInviteRole(event.target.value),
99
+ className: "rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-2 py-1.5 text-xs text-[var(--text-secondary)]",
100
+ children: ASSIGNABLE.map((option) => /* @__PURE__ */ jsx("option", { value: option.value, children: option.label }, option.value))
101
+ }
102
+ ),
103
+ /* @__PURE__ */ jsx(
104
+ "button",
105
+ {
106
+ type: "button",
107
+ onClick: () => void submitInvite(),
108
+ disabled: inviting || !inviteEmail.trim(),
109
+ className: "rounded bg-[var(--brand-primary)] px-3 py-1.5 text-sm text-white disabled:opacity-50",
110
+ children: inviting ? "Inviting\u2026" : "Invite"
111
+ }
112
+ )
113
+ ] })
114
+ ] })
115
+ ] });
116
+ }
117
+ function MemberRow({ member, canManage, onChangeRole, onRemove }) {
118
+ const pending = member.acceptedAt == null;
119
+ const label = member.name ?? member.email ?? "Unknown";
120
+ const initial = (member.name?.[0] ?? member.email?.[0] ?? "?").toUpperCase();
121
+ const isOwner = member.role === "owner";
122
+ const editable = canManage && !isOwner && !member.inherited;
123
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b border-[var(--border-default)] py-2 last:border-0", children: [
124
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
125
+ /* @__PURE__ */ jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-full bg-[var(--bg-input)] text-xs font-bold text-[var(--text-secondary)]", children: initial }),
126
+ /* @__PURE__ */ jsxs("div", { children: [
127
+ /* @__PURE__ */ jsxs("p", { className: "flex items-center gap-2 text-sm font-medium text-[var(--text-primary)]", children: [
128
+ label,
129
+ pending && /* @__PURE__ */ jsx("span", { className: "rounded border border-[var(--border-default)] px-1.5 py-0.5 text-[10px] uppercase text-[var(--text-muted)]", children: "Pending" })
130
+ ] }),
131
+ member.name && member.email && /* @__PURE__ */ jsx("p", { className: "text-xs text-[var(--text-muted)]", children: member.email })
132
+ ] })
133
+ ] }),
134
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: isOwner ? /* @__PURE__ */ jsx("span", { className: "rounded border border-[var(--border-default)] px-2 py-0.5 text-xs text-[var(--text-secondary)]", children: member.inherited ? "Org Admin" : "Owner" }) : editable ? /* @__PURE__ */ jsxs(Fragment, { children: [
135
+ /* @__PURE__ */ jsx(
136
+ "select",
137
+ {
138
+ value: member.role,
139
+ "aria-label": `Role for ${label}`,
140
+ onChange: (event) => onChangeRole(member.id, event.target.value),
141
+ className: "rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-2 py-1 text-xs text-[var(--text-secondary)]",
142
+ children: ASSIGNABLE.map((option) => /* @__PURE__ */ jsx("option", { value: option.value, children: option.label }, option.value))
143
+ }
144
+ ),
145
+ /* @__PURE__ */ jsx(
146
+ "button",
147
+ {
148
+ type: "button",
149
+ "aria-label": `Remove ${label}`,
150
+ onClick: () => onRemove(member.id),
151
+ className: "rounded px-2 py-1 text-xs text-[var(--text-danger)] hover:bg-[var(--border-default)]",
152
+ children: "Remove"
153
+ }
154
+ )
155
+ ] }) : /* @__PURE__ */ jsx("span", { className: "rounded border border-[var(--border-default)] px-2 py-0.5 text-xs capitalize text-[var(--text-secondary)]", children: member.role }) })
156
+ ] });
157
+ }
158
+
159
+ export {
160
+ MembersPanel
161
+ };
162
+ //# sourceMappingURL=chunk-3G334FVA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/teams-react/components/MembersPanel.tsx"],"sourcesContent":["/**\n * Workspace members panel: list members, invite by email + role, change a\n * member's role, remove a member. Fully callback-driven — the host supplies the\n * data and the async `onInvite`/`onChangeRole`/`onRemove` callbacks (backed by\n * `./teams/members-api`), so this imports no app router, fetch client, or toast.\n * Styled with the shipped Tangle Quiet tokens (`var(--*)`).\n *\n * Role gating mirrors the API: only admins/owners see role selects and the\n * remove control; inherited org owners and explicit owners are not editable\n * here (org-level concern). The invite role select offers `admin` only to\n * admins/owners.\n */\n\nimport { useState } from 'react'\nimport type { WorkspaceRole } from '../../teams/roles'\nimport { hasWorkspaceRole } from '../../teams/roles'\nimport type { MemberView, MembersPanelProps } from '../contracts'\n\nconst ASSIGNABLE: { value: WorkspaceRole; label: string }[] = [\n { value: 'viewer', label: 'Viewer' },\n { value: 'editor', label: 'Editor' },\n { value: 'admin', label: 'Admin' },\n]\n\nexport function MembersPanel({\n members,\n currentRole,\n onInvite,\n onChangeRole,\n onRemove,\n onNotice,\n}: MembersPanelProps) {\n const [inviteEmail, setInviteEmail] = useState('')\n const [inviteRole, setInviteRole] = useState<WorkspaceRole>('editor')\n const [inviting, setInviting] = useState(false)\n\n const canManage = hasWorkspaceRole(currentRole, 'admin')\n\n function notify(kind: 'success' | 'error', message: string) {\n onNotice?.({ kind, message })\n }\n\n async function submitInvite() {\n const email = inviteEmail.trim()\n if (!email || inviting) return\n setInviting(true)\n try {\n const result = await onInvite({ email, role: inviteRole })\n if (result && 'inviteUrl' in result && result.inviteUrl) {\n notify('success', `Invite link ready for ${email}`)\n } else {\n notify('success', `Invited ${email}`)\n }\n setInviteEmail('')\n } catch (err) {\n notify('error', err instanceof Error ? err.message : 'Failed to invite')\n } finally {\n setInviting(false)\n }\n }\n\n async function changeRole(memberId: string, role: WorkspaceRole) {\n try {\n await onChangeRole({ memberId, role })\n notify('success', 'Role updated')\n } catch (err) {\n notify('error', err instanceof Error ? err.message : 'Failed to update role')\n }\n }\n\n async function remove(memberId: string) {\n try {\n await onRemove({ memberId })\n notify('success', 'Member removed')\n } catch (err) {\n notify('error', err instanceof Error ? err.message : 'Failed to remove member')\n }\n }\n\n return (\n <section className=\"flex flex-col gap-4\">\n <div className=\"flex flex-col gap-2\">\n {members.map((member) => (\n <MemberRow\n key={member.id}\n member={member}\n canManage={canManage}\n onChangeRole={changeRole}\n onRemove={remove}\n />\n ))}\n {members.length === 0 && (\n <p className=\"text-sm text-[var(--text-muted)]\">No team members yet.</p>\n )}\n </div>\n\n {canManage && (\n <div className=\"flex flex-col gap-1.5 border-t border-[var(--border-default)] pt-3\">\n <label className=\"text-xs font-medium text-[var(--text-muted)]\">Invite member</label>\n <div className=\"flex gap-2\">\n <input\n type=\"email\"\n placeholder=\"Email address\"\n value={inviteEmail}\n aria-label=\"Invite email address\"\n onChange={(event) => setInviteEmail(event.target.value)}\n onKeyDown={(event) => { if (event.key === 'Enter') void submitInvite() }}\n className=\"flex-1 rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-3 py-1.5 text-sm text-[var(--text-primary)] placeholder:text-[var(--text-muted)]\"\n />\n <select\n value={inviteRole}\n aria-label=\"Invite role\"\n onChange={(event) => setInviteRole(event.target.value as WorkspaceRole)}\n className=\"rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-2 py-1.5 text-xs text-[var(--text-secondary)]\"\n >\n {ASSIGNABLE.map((option) => (\n <option key={option.value} value={option.value}>{option.label}</option>\n ))}\n </select>\n <button\n type=\"button\"\n onClick={() => void submitInvite()}\n disabled={inviting || !inviteEmail.trim()}\n className=\"rounded bg-[var(--brand-primary)] px-3 py-1.5 text-sm text-white disabled:opacity-50\"\n >\n {inviting ? 'Inviting…' : 'Invite'}\n </button>\n </div>\n </div>\n )}\n </section>\n )\n}\n\ninterface MemberRowProps {\n member: MemberView\n canManage: boolean\n onChangeRole(memberId: string, role: WorkspaceRole): void\n onRemove(memberId: string): void\n}\n\nfunction MemberRow({ member, canManage, onChangeRole, onRemove }: MemberRowProps) {\n const pending = member.acceptedAt == null\n const label = member.name ?? member.email ?? 'Unknown'\n const initial = (member.name?.[0] ?? member.email?.[0] ?? '?').toUpperCase()\n const isOwner = member.role === 'owner'\n const editable = canManage && !isOwner && !member.inherited\n\n return (\n <div className=\"flex items-center justify-between border-b border-[var(--border-default)] py-2 last:border-0\">\n <div className=\"flex items-center gap-3\">\n <div className=\"flex h-8 w-8 items-center justify-center rounded-full bg-[var(--bg-input)] text-xs font-bold text-[var(--text-secondary)]\">\n {initial}\n </div>\n <div>\n <p className=\"flex items-center gap-2 text-sm font-medium text-[var(--text-primary)]\">\n {label}\n {pending && (\n <span className=\"rounded border border-[var(--border-default)] px-1.5 py-0.5 text-[10px] uppercase text-[var(--text-muted)]\">\n Pending\n </span>\n )}\n </p>\n {member.name && member.email && (\n <p className=\"text-xs text-[var(--text-muted)]\">{member.email}</p>\n )}\n </div>\n </div>\n\n <div className=\"flex items-center gap-2\">\n {isOwner ? (\n <span className=\"rounded border border-[var(--border-default)] px-2 py-0.5 text-xs text-[var(--text-secondary)]\">\n {member.inherited ? 'Org Admin' : 'Owner'}\n </span>\n ) : editable ? (\n <>\n <select\n value={member.role}\n aria-label={`Role for ${label}`}\n onChange={(event) => onChangeRole(member.id, event.target.value as WorkspaceRole)}\n className=\"rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-2 py-1 text-xs text-[var(--text-secondary)]\"\n >\n {ASSIGNABLE.map((option) => (\n <option key={option.value} value={option.value}>{option.label}</option>\n ))}\n </select>\n <button\n type=\"button\"\n aria-label={`Remove ${label}`}\n onClick={() => onRemove(member.id)}\n className=\"rounded px-2 py-1 text-xs text-[var(--text-danger)] hover:bg-[var(--border-default)]\"\n >\n Remove\n </button>\n </>\n ) : (\n <span className=\"rounded border border-[var(--border-default)] px-2 py-0.5 text-xs capitalize text-[var(--text-secondary)]\">\n {member.role}\n </span>\n )}\n </div>\n </div>\n )\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,41 @@
1
+ // src/teams/invite.ts
2
+ var INVITE_TOKEN_BYTES = 24;
3
+ var INVITE_TOKEN_PATTERN = /^[A-Za-z0-9_-]{16,}$/;
4
+ function generateInviteToken() {
5
+ const bytes = new Uint8Array(INVITE_TOKEN_BYTES);
6
+ crypto.getRandomValues(bytes);
7
+ return base64UrlEncode(bytes);
8
+ }
9
+ function isInviteTokenShape(value) {
10
+ return typeof value === "string" && INVITE_TOKEN_PATTERN.test(value);
11
+ }
12
+ function validateInviteToken(invite, opts = {}) {
13
+ if (invite.acceptedAt != null) return { ok: false, reason: "already-accepted" };
14
+ if (invite.expiresAt != null) {
15
+ const now = (opts.now ?? /* @__PURE__ */ new Date()).getTime();
16
+ const expires = invite.expiresAt instanceof Date ? invite.expiresAt.getTime() : Number(invite.expiresAt);
17
+ if (Number.isFinite(expires) && now >= expires) return { ok: false, reason: "expired" };
18
+ }
19
+ if (invite.inviteEmail && opts.acceptingEmail) {
20
+ if (invite.inviteEmail.trim().toLowerCase() !== opts.acceptingEmail.trim().toLowerCase()) {
21
+ return { ok: false, reason: "email-mismatch" };
22
+ }
23
+ }
24
+ return { ok: true };
25
+ }
26
+ function base64UrlEncode(bytes) {
27
+ let binary = "";
28
+ for (const byte of bytes) binary += String.fromCharCode(byte);
29
+ const base64 = typeof btoa === "function" ? btoa(binary) : bufferToBase64(binary);
30
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
31
+ }
32
+ function bufferToBase64(binary) {
33
+ return Buffer.from(binary, "binary").toString("base64");
34
+ }
35
+
36
+ export {
37
+ generateInviteToken,
38
+ isInviteTokenShape,
39
+ validateInviteToken
40
+ };
41
+ //# sourceMappingURL=chunk-3SVAA3MA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/teams/invite.ts"],"sourcesContent":["/**\n * Pure invite-token helpers — generation, shape validation, and expiry math.\n * No I/O: the members API persists/looks up tokens; these functions only\n * produce well-formed tokens and decide, given values the caller already\n * loaded, whether an invite is usable.\n *\n * A token is an opaque high-entropy URL-safe string. It is the bearer secret\n * in `/invite/:token`, so it must be unguessable and never derived from the\n * email or workspace. `generateInviteToken` uses Web Crypto (`crypto`), which\n * is present in Workers, Node 18+, Deno, and browsers — no Node-only import,\n * so this stays a pure leaf.\n */\n\nconst INVITE_TOKEN_BYTES = 24\nconst INVITE_TOKEN_PATTERN = /^[A-Za-z0-9_-]{16,}$/\n\n/** Cryptographically-random, URL-safe (base64url) invite token. */\nexport function generateInviteToken(): string {\n const bytes = new Uint8Array(INVITE_TOKEN_BYTES)\n crypto.getRandomValues(bytes)\n return base64UrlEncode(bytes)\n}\n\n/** True when `value` has the shape of an invite token (not whether it exists). */\nexport function isInviteTokenShape(value: unknown): value is string {\n return typeof value === 'string' && INVITE_TOKEN_PATTERN.test(value)\n}\n\n/** A pending invite row, narrowed to the fields invite acceptance reasons over. */\nexport interface InviteTokenState {\n /** null until accepted — a non-null value means the invite was already used. */\n acceptedAt: Date | number | null | undefined\n /** Email the invite was addressed to, if any. */\n inviteEmail?: string | null\n /** Optional hard expiry; omit/undefined for invites that never expire. */\n expiresAt?: Date | number | null\n}\n\nexport type InviteRejectionReason = 'already-accepted' | 'expired' | 'email-mismatch'\n\nexport interface InviteValidationResult {\n ok: boolean\n reason?: InviteRejectionReason\n}\n\n/**\n * Decide whether a loaded invite can be accepted by `acceptingEmail` at `now`.\n * Pure: the caller has already fetched the row by token; this only judges it.\n * Email match is case-insensitive and only enforced when the invite was\n * addressed to a specific email (an open invite has no `inviteEmail`).\n */\nexport function validateInviteToken(\n invite: InviteTokenState,\n opts: { acceptingEmail?: string | null; now?: Date } = {},\n): InviteValidationResult {\n if (invite.acceptedAt != null) return { ok: false, reason: 'already-accepted' }\n\n if (invite.expiresAt != null) {\n const now = (opts.now ?? new Date()).getTime()\n const expires = invite.expiresAt instanceof Date ? invite.expiresAt.getTime() : Number(invite.expiresAt)\n if (Number.isFinite(expires) && now >= expires) return { ok: false, reason: 'expired' }\n }\n\n if (invite.inviteEmail && opts.acceptingEmail) {\n if (invite.inviteEmail.trim().toLowerCase() !== opts.acceptingEmail.trim().toLowerCase()) {\n return { ok: false, reason: 'email-mismatch' }\n }\n }\n\n return { ok: true }\n}\n\nfunction base64UrlEncode(bytes: Uint8Array): string {\n let binary = ''\n for (const byte of bytes) binary += String.fromCharCode(byte)\n const base64 = typeof btoa === 'function' ? btoa(binary) : bufferToBase64(binary)\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\nfunction bufferToBase64(binary: string): string {\n // Node without a global `btoa` (older runtimes); Buffer is always present there.\n return Buffer.from(binary, 'binary').toString('base64')\n}\n"],"mappings":";AAaA,IAAM,qBAAqB;AAC3B,IAAM,uBAAuB;AAGtB,SAAS,sBAA8B;AAC5C,QAAM,QAAQ,IAAI,WAAW,kBAAkB;AAC/C,SAAO,gBAAgB,KAAK;AAC5B,SAAO,gBAAgB,KAAK;AAC9B;AAGO,SAAS,mBAAmB,OAAiC;AAClE,SAAO,OAAO,UAAU,YAAY,qBAAqB,KAAK,KAAK;AACrE;AAyBO,SAAS,oBACd,QACA,OAAuD,CAAC,GAChC;AACxB,MAAI,OAAO,cAAc,KAAM,QAAO,EAAE,IAAI,OAAO,QAAQ,mBAAmB;AAE9E,MAAI,OAAO,aAAa,MAAM;AAC5B,UAAM,OAAO,KAAK,OAAO,oBAAI,KAAK,GAAG,QAAQ;AAC7C,UAAM,UAAU,OAAO,qBAAqB,OAAO,OAAO,UAAU,QAAQ,IAAI,OAAO,OAAO,SAAS;AACvG,QAAI,OAAO,SAAS,OAAO,KAAK,OAAO,QAAS,QAAO,EAAE,IAAI,OAAO,QAAQ,UAAU;AAAA,EACxF;AAEA,MAAI,OAAO,eAAe,KAAK,gBAAgB;AAC7C,QAAI,OAAO,YAAY,KAAK,EAAE,YAAY,MAAM,KAAK,eAAe,KAAK,EAAE,YAAY,GAAG;AACxF,aAAO,EAAE,IAAI,OAAO,QAAQ,iBAAiB;AAAA,IAC/C;AAAA,EACF;AAEA,SAAO,EAAE,IAAI,KAAK;AACpB;AAEA,SAAS,gBAAgB,OAA2B;AAClD,MAAI,SAAS;AACb,aAAW,QAAQ,MAAO,WAAU,OAAO,aAAa,IAAI;AAC5D,QAAM,SAAS,OAAO,SAAS,aAAa,KAAK,MAAM,IAAI,eAAe,MAAM;AAChF,SAAO,OAAO,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,EAAE;AACzE;AAEA,SAAS,eAAe,QAAwB;AAE9C,SAAO,OAAO,KAAK,QAAQ,QAAQ,EAAE,SAAS,QAAQ;AACxD;","names":[]}
@@ -0,0 +1,128 @@
1
+ // src/teams-react/components/InviteAcceptPage.tsx
2
+ import { useState } from "react";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ function InviteAcceptPage({ details, onAccept, onNavigate }) {
5
+ const [accepting, setAccepting] = useState(false);
6
+ const [acceptError, setAcceptError] = useState(null);
7
+ const [accepted, setAccepted] = useState(false);
8
+ if (details.status === "invalid") {
9
+ return /* @__PURE__ */ jsx(Shell, { title: "Invalid invite", body: "This invite link is invalid or has expired.", children: /* @__PURE__ */ jsx(PrimaryButton, { onClick: () => onNavigate({ kind: "sign-in" }), children: "Go to sign in" }) });
10
+ }
11
+ if (details.status === "already-accepted") {
12
+ return /* @__PURE__ */ jsx(Shell, { title: "Already accepted", body: "This invite has already been accepted.", children: /* @__PURE__ */ jsx(PrimaryButton, { onClick: () => onNavigate({ kind: "open-app" }), children: "Open workspace" }) });
13
+ }
14
+ const workspaceName = details.workspaceName ?? "a workspace";
15
+ const inviterPrefix = details.inviterName ? `${details.inviterName} invited you` : "You've been invited";
16
+ const roleSuffix = details.role ? ` as ${details.role}` : "";
17
+ if (!details.currentUserEmail) {
18
+ return /* @__PURE__ */ jsxs(Shell, { title: "You've been invited", children: [
19
+ /* @__PURE__ */ jsxs("p", { className: "mb-4 text-sm text-[var(--text-secondary)]", children: [
20
+ inviterPrefix,
21
+ " to join ",
22
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-[var(--text-primary)]", children: workspaceName }),
23
+ roleSuffix,
24
+ "."
25
+ ] }),
26
+ details.inviteEmail && /* @__PURE__ */ jsxs("p", { className: "mb-6 text-sm text-[var(--text-secondary)]", children: [
27
+ "Sign in or create an account with",
28
+ " ",
29
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-[var(--text-primary)]", children: details.inviteEmail }),
30
+ " to accept."
31
+ ] }),
32
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
33
+ /* @__PURE__ */ jsx(PrimaryButton, { onClick: () => onNavigate({ kind: "sign-in" }), children: "Sign in" }),
34
+ /* @__PURE__ */ jsx(SecondaryButton, { onClick: () => onNavigate({ kind: "sign-up" }), children: "Create account" })
35
+ ] })
36
+ ] });
37
+ }
38
+ if (accepted) {
39
+ return /* @__PURE__ */ jsx(Shell, { title: "Welcome!", children: /* @__PURE__ */ jsxs("p", { className: "text-sm text-[var(--text-secondary)]", children: [
40
+ "You've joined ",
41
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-[var(--text-primary)]", children: workspaceName }),
42
+ "."
43
+ ] }) });
44
+ }
45
+ const emailMismatch = Boolean(
46
+ details.inviteEmail && details.inviteEmail.toLowerCase() !== details.currentUserEmail.toLowerCase()
47
+ );
48
+ async function handleAccept() {
49
+ setAccepting(true);
50
+ setAcceptError(null);
51
+ try {
52
+ const result = await onAccept();
53
+ setAccepted(true);
54
+ if (result && "workspaceId" in result && result.workspaceId) {
55
+ onNavigate({ kind: "open-app", workspaceId: result.workspaceId });
56
+ }
57
+ } catch (err) {
58
+ setAcceptError(err instanceof Error ? err.message : "Failed to accept invite");
59
+ } finally {
60
+ setAccepting(false);
61
+ }
62
+ }
63
+ return /* @__PURE__ */ jsxs(Shell, { title: "Join workspace", children: [
64
+ /* @__PURE__ */ jsxs("p", { className: "mb-4 text-sm text-[var(--text-secondary)]", children: [
65
+ inviterPrefix,
66
+ " to join ",
67
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-[var(--text-primary)]", children: workspaceName }),
68
+ roleSuffix,
69
+ "."
70
+ ] }),
71
+ /* @__PURE__ */ jsxs("p", { className: "mb-4 text-sm text-[var(--text-secondary)]", children: [
72
+ "Signed in as ",
73
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-[var(--text-primary)]", children: details.currentUserEmail })
74
+ ] }),
75
+ emailMismatch && /* @__PURE__ */ jsxs(
76
+ "div",
77
+ {
78
+ role: "alert",
79
+ className: "mb-4 rounded-md border border-[var(--border-default)] px-4 py-2 text-sm text-[var(--text-warning)]",
80
+ children: [
81
+ "This invite was sent to ",
82
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: details.inviteEmail }),
83
+ ". Switch to that account to accept it."
84
+ ]
85
+ }
86
+ ),
87
+ acceptError && /* @__PURE__ */ jsx("p", { role: "alert", className: "mb-4 text-sm text-[var(--text-danger)]", children: acceptError }),
88
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
89
+ emailMismatch ? /* @__PURE__ */ jsx(PrimaryButton, { onClick: () => onNavigate({ kind: "switch-account" }), children: "Switch account" }) : /* @__PURE__ */ jsx(PrimaryButton, { onClick: () => void handleAccept(), disabled: accepting, children: accepting ? "Accepting\u2026" : "Accept invite" }),
90
+ /* @__PURE__ */ jsx(SecondaryButton, { onClick: () => onNavigate({ kind: "open-app" }), children: "Not now" })
91
+ ] })
92
+ ] });
93
+ }
94
+ function Shell({ title, body, children }) {
95
+ return /* @__PURE__ */ jsxs("div", { className: "mx-auto flex w-full max-w-sm flex-col", children: [
96
+ /* @__PURE__ */ jsx("h1", { className: "mb-1 text-xl font-semibold tracking-tight text-[var(--text-primary)]", children: title }),
97
+ body && /* @__PURE__ */ jsx("p", { className: "mb-6 text-sm text-[var(--text-secondary)]", children: body }),
98
+ children
99
+ ] });
100
+ }
101
+ function PrimaryButton({ children, onClick, disabled }) {
102
+ return /* @__PURE__ */ jsx(
103
+ "button",
104
+ {
105
+ type: "button",
106
+ onClick,
107
+ disabled,
108
+ className: "flex-1 rounded bg-[var(--brand-primary)] px-4 py-2 text-sm text-white disabled:opacity-50",
109
+ children
110
+ }
111
+ );
112
+ }
113
+ function SecondaryButton({ children, onClick }) {
114
+ return /* @__PURE__ */ jsx(
115
+ "button",
116
+ {
117
+ type: "button",
118
+ onClick,
119
+ className: "flex-1 rounded border border-[var(--border-default)] px-4 py-2 text-sm text-[var(--text-secondary)] hover:text-[var(--text-primary)]",
120
+ children
121
+ }
122
+ );
123
+ }
124
+
125
+ export {
126
+ InviteAcceptPage
127
+ };
128
+ //# sourceMappingURL=chunk-535V6BH6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/teams-react/components/InviteAcceptPage.tsx"],"sourcesContent":["/**\n * Invite-accept surface for `/invite/:token`. Renders the invite state (valid /\n * invalid / already-accepted), handles the signed-out path (sign in or create\n * an account with the invited email), the email-mismatch path (switch account),\n * and the accept action. Callback-driven: the host supplies the resolved\n * `details` and `onAccept` / `onNavigate`, so this imports no app router or\n * fetch client. Styled with the shipped Tangle Quiet tokens (`var(--*)`).\n */\n\nimport { useState } from 'react'\nimport type { InviteAcceptPageProps } from '../contracts'\n\nexport function InviteAcceptPage({ details, onAccept, onNavigate }: InviteAcceptPageProps) {\n const [accepting, setAccepting] = useState(false)\n const [acceptError, setAcceptError] = useState<string | null>(null)\n const [accepted, setAccepted] = useState(false)\n\n if (details.status === 'invalid') {\n return (\n <Shell title=\"Invalid invite\" body=\"This invite link is invalid or has expired.\">\n <PrimaryButton onClick={() => onNavigate({ kind: 'sign-in' })}>Go to sign in</PrimaryButton>\n </Shell>\n )\n }\n\n if (details.status === 'already-accepted') {\n return (\n <Shell title=\"Already accepted\" body=\"This invite has already been accepted.\">\n <PrimaryButton onClick={() => onNavigate({ kind: 'open-app' })}>Open workspace</PrimaryButton>\n </Shell>\n )\n }\n\n const workspaceName = details.workspaceName ?? 'a workspace'\n const inviterPrefix = details.inviterName ? `${details.inviterName} invited you` : \"You've been invited\"\n const roleSuffix = details.role ? ` as ${details.role}` : ''\n\n if (!details.currentUserEmail) {\n return (\n <Shell title=\"You've been invited\">\n <p className=\"mb-4 text-sm text-[var(--text-secondary)]\">\n {inviterPrefix} to join <span className=\"font-medium text-[var(--text-primary)]\">{workspaceName}</span>{roleSuffix}.\n </p>\n {details.inviteEmail && (\n <p className=\"mb-6 text-sm text-[var(--text-secondary)]\">\n Sign in or create an account with{' '}\n <span className=\"font-medium text-[var(--text-primary)]\">{details.inviteEmail}</span> to accept.\n </p>\n )}\n <div className=\"flex gap-2\">\n <PrimaryButton onClick={() => onNavigate({ kind: 'sign-in' })}>Sign in</PrimaryButton>\n <SecondaryButton onClick={() => onNavigate({ kind: 'sign-up' })}>Create account</SecondaryButton>\n </div>\n </Shell>\n )\n }\n\n if (accepted) {\n return (\n <Shell title=\"Welcome!\">\n <p className=\"text-sm text-[var(--text-secondary)]\">\n You've joined <span className=\"font-medium text-[var(--text-primary)]\">{workspaceName}</span>.\n </p>\n </Shell>\n )\n }\n\n const emailMismatch = Boolean(\n details.inviteEmail &&\n details.inviteEmail.toLowerCase() !== details.currentUserEmail.toLowerCase(),\n )\n\n async function handleAccept() {\n setAccepting(true)\n setAcceptError(null)\n try {\n const result = await onAccept()\n setAccepted(true)\n if (result && 'workspaceId' in result && result.workspaceId) {\n onNavigate({ kind: 'open-app', workspaceId: result.workspaceId })\n }\n } catch (err) {\n setAcceptError(err instanceof Error ? err.message : 'Failed to accept invite')\n } finally {\n setAccepting(false)\n }\n }\n\n return (\n <Shell title=\"Join workspace\">\n <p className=\"mb-4 text-sm text-[var(--text-secondary)]\">\n {inviterPrefix} to join <span className=\"font-medium text-[var(--text-primary)]\">{workspaceName}</span>{roleSuffix}.\n </p>\n <p className=\"mb-4 text-sm text-[var(--text-secondary)]\">\n Signed in as <span className=\"font-medium text-[var(--text-primary)]\">{details.currentUserEmail}</span>\n </p>\n {emailMismatch && (\n <div\n role=\"alert\"\n className=\"mb-4 rounded-md border border-[var(--border-default)] px-4 py-2 text-sm text-[var(--text-warning)]\"\n >\n This invite was sent to <span className=\"font-medium\">{details.inviteEmail}</span>. Switch to that account to accept it.\n </div>\n )}\n {acceptError && (\n <p role=\"alert\" className=\"mb-4 text-sm text-[var(--text-danger)]\">{acceptError}</p>\n )}\n <div className=\"flex gap-2\">\n {emailMismatch ? (\n <PrimaryButton onClick={() => onNavigate({ kind: 'switch-account' })}>Switch account</PrimaryButton>\n ) : (\n <PrimaryButton onClick={() => void handleAccept()} disabled={accepting}>\n {accepting ? 'Accepting…' : 'Accept invite'}\n </PrimaryButton>\n )}\n <SecondaryButton onClick={() => onNavigate({ kind: 'open-app' })}>Not now</SecondaryButton>\n </div>\n </Shell>\n )\n}\n\nfunction Shell({ title, body, children }: { title: string; body?: string; children: React.ReactNode }) {\n return (\n <div className=\"mx-auto flex w-full max-w-sm flex-col\">\n <h1 className=\"mb-1 text-xl font-semibold tracking-tight text-[var(--text-primary)]\">{title}</h1>\n {body && <p className=\"mb-6 text-sm text-[var(--text-secondary)]\">{body}</p>}\n {children}\n </div>\n )\n}\n\nfunction PrimaryButton({ children, onClick, disabled }: { children: React.ReactNode; onClick(): void; disabled?: boolean }) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n disabled={disabled}\n className=\"flex-1 rounded bg-[var(--brand-primary)] px-4 py-2 text-sm text-white disabled:opacity-50\"\n >\n {children}\n </button>\n )\n}\n\nfunction SecondaryButton({ children, onClick }: { children: React.ReactNode; onClick(): void }) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n className=\"flex-1 rounded border border-[var(--border-default)] px-4 py-2 text-sm text-[var(--text-secondary)] hover:text-[var(--text-primary)]\"\n >\n {children}\n </button>\n )\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,63 @@
1
+ // src/teams/roles.ts
2
+ var WORKSPACE_ROLES = ["viewer", "editor", "admin", "owner"];
3
+ var ASSIGNABLE_WORKSPACE_ROLES = ["viewer", "editor", "admin"];
4
+ var ORGANIZATION_ROLES = ["owner", "admin", "member", "billing"];
5
+ var WORKSPACE_ROLE_RANK = {
6
+ viewer: 0,
7
+ editor: 1,
8
+ admin: 2,
9
+ owner: 3
10
+ };
11
+ var ORGANIZATION_ROLE_RANK = {
12
+ member: 0,
13
+ billing: 1,
14
+ admin: 2,
15
+ owner: 3
16
+ };
17
+ function hasWorkspaceRole(actual, minimum) {
18
+ return WORKSPACE_ROLE_RANK[actual] >= WORKSPACE_ROLE_RANK[minimum];
19
+ }
20
+ function hasOrganizationRole(actual, minimum) {
21
+ return ORGANIZATION_ROLE_RANK[actual] >= ORGANIZATION_ROLE_RANK[minimum];
22
+ }
23
+ function isAssignableWorkspaceRole(value) {
24
+ return typeof value === "string" && ASSIGNABLE_WORKSPACE_ROLES.includes(value);
25
+ }
26
+ function organizationRoleGrantsWorkspaceOwner(role) {
27
+ return role === "owner" || role === "admin";
28
+ }
29
+ function resolveWorkspaceRole(organizationRole, workspaceRole) {
30
+ return organizationRoleGrantsWorkspaceOwner(organizationRole) ? "owner" : workspaceRole ?? null;
31
+ }
32
+ function canManageWorkspaceMemberRole(actorRole, targetRole) {
33
+ return actorRole === "owner" || !hasWorkspaceRole(targetRole, actorRole);
34
+ }
35
+ function workspaceRoleToCollaborationAccess(role) {
36
+ return role === "viewer" ? "read" : "write";
37
+ }
38
+ function workspaceRoleToSandboxRole(role) {
39
+ const mapping = {
40
+ owner: "owner",
41
+ admin: "admin",
42
+ editor: "developer",
43
+ viewer: "viewer"
44
+ };
45
+ return mapping[role];
46
+ }
47
+
48
+ export {
49
+ WORKSPACE_ROLES,
50
+ ASSIGNABLE_WORKSPACE_ROLES,
51
+ ORGANIZATION_ROLES,
52
+ WORKSPACE_ROLE_RANK,
53
+ ORGANIZATION_ROLE_RANK,
54
+ hasWorkspaceRole,
55
+ hasOrganizationRole,
56
+ isAssignableWorkspaceRole,
57
+ organizationRoleGrantsWorkspaceOwner,
58
+ resolveWorkspaceRole,
59
+ canManageWorkspaceMemberRole,
60
+ workspaceRoleToCollaborationAccess,
61
+ workspaceRoleToSandboxRole
62
+ };
63
+ //# sourceMappingURL=chunk-63CE7FEZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/teams/roles.ts"],"sourcesContent":["/**\n * Pure role algebra for the teams capability — the tenancy/membership model\n * shared across the fleet. Zero dependencies: no drizzle, no env, no react, no\n * I/O. The DB layer (`./teams/drizzle`), the members API (`./teams/members-api`)\n * and the React surface (`./teams-react`) all build on these functions; this\n * leaf imports nothing back, so a consumer can pull just the role math.\n *\n * Two role ladders, deliberately distinct:\n * - Organization roles rank the tenant (who owns/administers the org and its\n * billing). An org owner/admin is an owner of every workspace under it.\n * - Workspace roles rank a single workspace. They are the access primitive\n * every route checks via `hasWorkspaceRole(actual, minimum)`.\n *\n * `resolveWorkspaceRole` is the bridge: it folds the org role and the\n * per-workspace role into the one effective workspace role a request runs at.\n */\n\nexport const WORKSPACE_ROLES = ['viewer', 'editor', 'admin', 'owner'] as const\nexport type WorkspaceRole = typeof WORKSPACE_ROLES[number]\n\nexport const ASSIGNABLE_WORKSPACE_ROLES = ['viewer', 'editor', 'admin'] as const\nexport type AssignableWorkspaceRole = typeof ASSIGNABLE_WORKSPACE_ROLES[number]\n\nexport const ORGANIZATION_ROLES = ['owner', 'admin', 'member', 'billing'] as const\nexport type OrganizationRole = typeof ORGANIZATION_ROLES[number]\n\nexport type WorkspaceCollaborationAccess = 'read' | 'write'\nexport type SandboxWorkspaceRole = 'owner' | 'admin' | 'developer' | 'viewer'\n\nexport const WORKSPACE_ROLE_RANK: Record<WorkspaceRole, number> = {\n viewer: 0,\n editor: 1,\n admin: 2,\n owner: 3,\n}\n\nexport const ORGANIZATION_ROLE_RANK: Record<OrganizationRole, number> = {\n member: 0,\n billing: 1,\n admin: 2,\n owner: 3,\n}\n\n/** True when `actual` is at least `minimum` on the workspace ladder. */\nexport function hasWorkspaceRole(actual: WorkspaceRole, minimum: WorkspaceRole): boolean {\n return WORKSPACE_ROLE_RANK[actual] >= WORKSPACE_ROLE_RANK[minimum]\n}\n\n/** True when `actual` is at least `minimum` on the organization ladder. */\nexport function hasOrganizationRole(actual: OrganizationRole, minimum: OrganizationRole): boolean {\n return ORGANIZATION_ROLE_RANK[actual] >= ORGANIZATION_ROLE_RANK[minimum]\n}\n\nexport function isAssignableWorkspaceRole(value: unknown): value is AssignableWorkspaceRole {\n return typeof value === 'string' && ASSIGNABLE_WORKSPACE_ROLES.includes(value as AssignableWorkspaceRole)\n}\n\n/** Org owners and admins are workspace owners across the whole org. */\nexport function organizationRoleGrantsWorkspaceOwner(role: OrganizationRole | string | null | undefined): boolean {\n return role === 'owner' || role === 'admin'\n}\n\n/**\n * The effective workspace role a request runs at: org owner/admin → owner of\n * every workspace; otherwise the explicit per-workspace role (or null = no\n * access). This is the single fold every access check goes through.\n */\nexport function resolveWorkspaceRole(\n organizationRole: OrganizationRole | string | null | undefined,\n workspaceRole: WorkspaceRole | null | undefined,\n): WorkspaceRole | null {\n return organizationRoleGrantsWorkspaceOwner(organizationRole) ? 'owner' : workspaceRole ?? null\n}\n\n/**\n * Whether `actorRole` may set/clear a member currently at `targetRole`. Owners\n * can manage anyone; everyone else can only manage members strictly below\n * their own rank (an admin cannot demote another admin or an owner).\n */\nexport function canManageWorkspaceMemberRole(actorRole: WorkspaceRole, targetRole: WorkspaceRole): boolean {\n return actorRole === 'owner' || !hasWorkspaceRole(targetRole, actorRole)\n}\n\nexport function workspaceRoleToCollaborationAccess(role: WorkspaceRole): WorkspaceCollaborationAccess {\n return role === 'viewer' ? 'read' : 'write'\n}\n\nexport function workspaceRoleToSandboxRole(role: WorkspaceRole): SandboxWorkspaceRole {\n const mapping: Record<WorkspaceRole, SandboxWorkspaceRole> = {\n owner: 'owner',\n admin: 'admin',\n editor: 'developer',\n viewer: 'viewer',\n }\n return mapping[role]\n}\n"],"mappings":";AAiBO,IAAM,kBAAkB,CAAC,UAAU,UAAU,SAAS,OAAO;AAG7D,IAAM,6BAA6B,CAAC,UAAU,UAAU,OAAO;AAG/D,IAAM,qBAAqB,CAAC,SAAS,SAAS,UAAU,SAAS;AAMjE,IAAM,sBAAqD;AAAA,EAChE,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,OAAO;AACT;AAEO,IAAM,yBAA2D;AAAA,EACtE,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,OAAO;AAAA,EACP,OAAO;AACT;AAGO,SAAS,iBAAiB,QAAuB,SAAiC;AACvF,SAAO,oBAAoB,MAAM,KAAK,oBAAoB,OAAO;AACnE;AAGO,SAAS,oBAAoB,QAA0B,SAAoC;AAChG,SAAO,uBAAuB,MAAM,KAAK,uBAAuB,OAAO;AACzE;AAEO,SAAS,0BAA0B,OAAkD;AAC1F,SAAO,OAAO,UAAU,YAAY,2BAA2B,SAAS,KAAgC;AAC1G;AAGO,SAAS,qCAAqC,MAA6D;AAChH,SAAO,SAAS,WAAW,SAAS;AACtC;AAOO,SAAS,qBACd,kBACA,eACsB;AACtB,SAAO,qCAAqC,gBAAgB,IAAI,UAAU,iBAAiB;AAC7F;AAOO,SAAS,6BAA6B,WAA0B,YAAoC;AACzG,SAAO,cAAc,WAAW,CAAC,iBAAiB,YAAY,SAAS;AACzE;AAEO,SAAS,mCAAmC,MAAmD;AACpG,SAAO,SAAS,WAAW,SAAS;AACtC;AAEO,SAAS,2BAA2B,MAA2C;AACpF,QAAM,UAAuD;AAAA,IAC3D,OAAO;AAAA,IACP,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AACA,SAAO,QAAQ,IAAI;AACrB;","names":[]}
@@ -1,29 +1,29 @@
1
1
  // src/theme/theme.ts
2
2
  var lightTheme = {
3
3
  background: "0 0% 100%",
4
- foreground: "222 47% 11%",
5
- card: "0 0% 100%",
6
- cardForeground: "222 47% 11%",
4
+ foreground: "0 0% 5%",
5
+ card: "240 7% 97%",
6
+ cardForeground: "0 0% 5%",
7
7
  popover: "0 0% 100%",
8
- popoverForeground: "222 47% 11%",
9
- primary: "221 83% 47%",
8
+ popoverForeground: "0 0% 5%",
9
+ primary: "245 62% 57%",
10
10
  primaryForeground: "0 0% 100%",
11
- secondary: "210 40% 96%",
12
- secondaryForeground: "222 47% 30%",
13
- muted: "210 40% 96%",
14
- mutedForeground: "215 16% 44%",
15
- accent: "210 40% 96%",
16
- accentForeground: "222 47% 11%",
17
- destructive: "0 72% 45%",
11
+ secondary: "240 6% 93%",
12
+ secondaryForeground: "0 0% 10%",
13
+ muted: "240 6% 93%",
14
+ mutedForeground: "240 5% 38%",
15
+ accent: "240 6% 93%",
16
+ accentForeground: "0 0% 10%",
17
+ destructive: "0 72% 41%",
18
18
  destructiveForeground: "0 0% 100%",
19
- border: "214 32% 91%",
20
- input: "214 32% 91%",
21
- ring: "221 83% 53%",
22
- success: "142 72% 29%",
19
+ border: "240 6% 89%",
20
+ input: "240 6% 89%",
21
+ ring: "245 62% 57%",
22
+ success: "160 84% 26%",
23
23
  successForeground: "0 0% 100%",
24
- warning: "38 92% 32%",
24
+ warning: "41 96% 38%",
25
25
  warningForeground: "38 92% 12%",
26
- canvasBackdrop: "hsl(220 13% 91%)",
26
+ canvasBackdrop: "hsl(240 7% 90%)",
27
27
  canvasRender: {
28
28
  grid: "#c0c0c0",
29
29
  snapGrid: "#a0a0a0",
@@ -39,30 +39,30 @@ var lightTheme = {
39
39
  }
40
40
  };
41
41
  var darkTheme = {
42
- background: "222 47% 7%",
43
- foreground: "210 40% 98%",
44
- card: "222 47% 11%",
45
- cardForeground: "210 40% 98%",
46
- popover: "222 47% 11%",
47
- popoverForeground: "210 40% 98%",
48
- primary: "217 91% 72%",
49
- primaryForeground: "222 47% 11%",
50
- secondary: "217 33% 17%",
51
- secondaryForeground: "210 40% 90%",
52
- muted: "217 33% 17%",
53
- mutedForeground: "215 20% 65%",
54
- accent: "217 33% 17%",
55
- accentForeground: "210 40% 98%",
56
- destructive: "0 78% 67%",
42
+ background: "240 8% 5%",
43
+ foreground: "240 6% 93%",
44
+ card: "240 5% 8%",
45
+ cardForeground: "240 6% 93%",
46
+ popover: "240 4% 13%",
47
+ popoverForeground: "240 6% 93%",
48
+ primary: "239 84% 74%",
49
+ primaryForeground: "0 0% 100%",
50
+ secondary: "240 5% 11%",
51
+ secondaryForeground: "240 6% 93%",
52
+ muted: "240 5% 11%",
53
+ mutedForeground: "240 4% 62%",
54
+ accent: "240 5% 11%",
55
+ accentForeground: "240 6% 93%",
56
+ destructive: "348 90% 68%",
57
57
  destructiveForeground: "0 0% 12%",
58
- border: "217 33% 20%",
59
- input: "217 33% 20%",
60
- ring: "217 91% 60%",
61
- success: "142 60% 50%",
62
- successForeground: "142 80% 12%",
63
- warning: "38 95% 58%",
58
+ border: "240 3% 13%",
59
+ input: "240 5% 11%",
60
+ ring: "239 84% 74%",
61
+ success: "160 70% 52%",
62
+ successForeground: "160 84% 10%",
63
+ warning: "40 94% 56%",
64
64
  warningForeground: "38 92% 12%",
65
- canvasBackdrop: "hsl(0 0% 10%)",
65
+ canvasBackdrop: "hsl(240 8% 5%)",
66
66
  canvasRender: {
67
67
  grid: "#3a3a3a",
68
68
  snapGrid: "#5a5a5a",
@@ -122,4 +122,4 @@ export {
122
122
  themeColor,
123
123
  themeToCssVars
124
124
  };
125
- //# sourceMappingURL=chunk-Y2SZVW35.js.map
125
+ //# sourceMappingURL=chunk-LVD37K5V.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/theme/theme.ts"],"sourcesContent":["/**\n * Typed mirror of tokens.css for runtime/JS theming. The canonical source is\n * tokens.css (`import '@tangle-network/agent-app/styles'`); this module is for\n * apps that compute theme variables in JS, or read color values where CSS\n * custom properties cannot reach — notably Konva canvas render code, which\n * paints to a bitmap and cannot resolve `var(--…)`.\n *\n * Values are shadcn-style HSL channel triples (\"H S% L%\"); wrap with `color()`.\n */\n\nexport interface AgentAppTheme {\n background: string\n foreground: string\n card: string\n cardForeground: string\n popover: string\n popoverForeground: string\n primary: string\n primaryForeground: string\n secondary: string\n secondaryForeground: string\n muted: string\n mutedForeground: string\n accent: string\n accentForeground: string\n destructive: string\n destructiveForeground: string\n border: string\n input: string\n ring: string\n success: string\n successForeground: string\n warning: string\n warningForeground: string\n /** Full CSS color (not a triple) — the canvas/scene backdrop. */\n canvasBackdrop: string\n /** Konva render palette — full hex colors the bitmap canvas paints with\n * (it cannot resolve `var(--…)`). NOT emitted by themeToCssVars. */\n canvasRender: CanvasRenderPalette\n}\n\n/**\n * Colors the Konva design-canvas paints directly. Konva renders to a bitmap\n * and cannot read CSS custom properties, so these are full hex strings sourced\n * from the active theme and threaded through the canvas components.\n */\nexport interface CanvasRenderPalette {\n /** Grid line color (GridLayer). */\n grid: string\n /** Grid-snap guide line (SnapGuidesOverlay, kind 'grid'). */\n snapGrid: string\n /** Saved ruler-guide snap line (kind 'guide'). */\n snapGuide: string\n /** Page edge/center snap line (kinds 'page-edge'/'page-center'). */\n snapPage: string\n /** Element edge/center snap line (kinds 'element-edge'/'element-center'). */\n snapElement: string\n /** Transformer border + anchor stroke (SelectionLayer). */\n selectionStroke: string\n /** Transformer anchor fill (SelectionLayer). */\n selectionAnchorFill: string\n /** Video placeholder fill (ElementNode VideoNode). */\n placeholderFill: string\n /** Video placeholder stroke (ElementNode VideoNode). */\n placeholderStroke: string\n /** Broken/loading image placeholder fill (ElementNode ImageNode). */\n brokenFill: string\n /** Broken/loading image placeholder stroke (ElementNode ImageNode). */\n brokenStroke: string\n}\n\nexport const lightTheme: AgentAppTheme = {\n background: '0 0% 100%',\n foreground: '0 0% 5%',\n card: '240 7% 97%',\n cardForeground: '0 0% 5%',\n popover: '0 0% 100%',\n popoverForeground: '0 0% 5%',\n primary: '245 62% 57%',\n primaryForeground: '0 0% 100%',\n secondary: '240 6% 93%',\n secondaryForeground: '0 0% 10%',\n muted: '240 6% 93%',\n mutedForeground: '240 5% 38%',\n accent: '240 6% 93%',\n accentForeground: '0 0% 10%',\n destructive: '0 72% 41%',\n destructiveForeground: '0 0% 100%',\n border: '240 6% 89%',\n input: '240 6% 89%',\n ring: '245 62% 57%',\n success: '160 84% 26%',\n successForeground: '0 0% 100%',\n warning: '41 96% 38%',\n warningForeground: '38 92% 12%',\n canvasBackdrop: 'hsl(240 7% 90%)',\n canvasRender: {\n grid: '#c0c0c0',\n snapGrid: '#a0a0a0',\n snapGuide: '#3b82f6',\n snapPage: '#f59e0b',\n snapElement: '#f43f5e',\n selectionStroke: '#00a1ff',\n selectionAnchorFill: '#ffffff',\n placeholderFill: '#1f2937',\n placeholderStroke: '#374151',\n brokenFill: '#e5e7eb',\n brokenStroke: '#9ca3af',\n },\n}\n\nexport const darkTheme: AgentAppTheme = {\n background: '240 8% 5%',\n foreground: '240 6% 93%',\n card: '240 5% 8%',\n cardForeground: '240 6% 93%',\n popover: '240 4% 13%',\n popoverForeground: '240 6% 93%',\n primary: '239 84% 74%',\n primaryForeground: '0 0% 100%',\n secondary: '240 5% 11%',\n secondaryForeground: '240 6% 93%',\n muted: '240 5% 11%',\n mutedForeground: '240 4% 62%',\n accent: '240 5% 11%',\n accentForeground: '240 6% 93%',\n destructive: '348 90% 68%',\n destructiveForeground: '0 0% 12%',\n border: '240 3% 13%',\n input: '240 5% 11%',\n ring: '239 84% 74%',\n success: '160 70% 52%',\n successForeground: '160 84% 10%',\n warning: '40 94% 56%',\n warningForeground: '38 92% 12%',\n canvasBackdrop: 'hsl(240 8% 5%)',\n canvasRender: {\n grid: '#3a3a3a',\n snapGrid: '#5a5a5a',\n snapGuide: '#3b82f6',\n snapPage: '#f59e0b',\n snapElement: '#f43f5e',\n selectionStroke: '#00a1ff',\n selectionAnchorFill: '#e5e7eb',\n placeholderFill: '#2a2f3a',\n placeholderStroke: '#3f4654',\n brokenFill: '#262b33',\n brokenStroke: '#4b5563',\n },\n}\n\n/** Wrap a channel triple in `hsl()`; pass through values already in a color form. */\nexport function themeColor(value: string): string {\n return value.startsWith('hsl') || value.startsWith('#') || value.startsWith('rgb')\n ? value\n : `hsl(${value})`\n}\n\n/**\n * Map a theme to the full CSS-variable set (shadcn triples + canvas/sequences\n * aliases + canvas surface). Apply at runtime to scope a theme without loading\n * tokens.css: `Object.assign(el.style, themeToCssVars(darkTheme))`.\n */\nexport function themeToCssVars(theme: AgentAppTheme): Record<string, string> {\n return {\n '--background': theme.background,\n '--foreground': theme.foreground,\n '--card': theme.card,\n '--card-foreground': theme.cardForeground,\n '--popover': theme.popover,\n '--popover-foreground': theme.popoverForeground,\n '--primary': theme.primary,\n '--primary-foreground': theme.primaryForeground,\n '--secondary': theme.secondary,\n '--secondary-foreground': theme.secondaryForeground,\n '--muted': theme.muted,\n '--muted-foreground': theme.mutedForeground,\n '--accent': theme.accent,\n '--accent-foreground': theme.accentForeground,\n '--destructive': theme.destructive,\n '--destructive-foreground': theme.destructiveForeground,\n '--border': theme.border,\n '--input': theme.input,\n '--ring': theme.ring,\n '--success': theme.success,\n '--success-foreground': theme.successForeground,\n '--warning': theme.warning,\n '--warning-foreground': theme.warningForeground,\n '--bg-input': `hsl(${theme.card})`,\n '--text-primary': `hsl(${theme.foreground})`,\n '--text-secondary': `hsl(${theme.secondaryForeground})`,\n '--text-muted': `hsl(${theme.mutedForeground})`,\n '--text-danger': `hsl(${theme.destructive})`,\n '--border-default': `hsl(${theme.border})`,\n '--brand-primary': `hsl(${theme.primary})`,\n '--canvas-backdrop': theme.canvasBackdrop,\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  // src/design-canvas-react/lazy.tsx
2
2
  import { lazy } from "react";
3
3
  var DesignCanvasLazy = lazy(
4
- () => import("./DesignCanvasEditor-TN7CWUJW.js").then((m) => ({ default: m.DesignCanvasEditor }))
4
+ () => import("./DesignCanvasEditor-QQNAZRJQ.js").then((m) => ({ default: m.DesignCanvasEditor }))
5
5
  );
6
6
  var DesignCanvasChromeLazy = lazy(
7
7
  () => import("./DesignCanvas-AOIVN3SJ.js").then((m) => ({ default: m.DesignCanvas }))
@@ -11,4 +11,4 @@ export {
11
11
  DesignCanvasLazy,
12
12
  DesignCanvasChromeLazy
13
13
  };
14
- //# sourceMappingURL=chunk-J7JNV5BH.js.map
14
+ //# sourceMappingURL=chunk-WOVCWPFF.js.map