@happyvertical/smrt-messages 0.31.0 → 0.31.1

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 (28) hide show
  1. package/dist/index.js +49 -7
  2. package/dist/index.js.map +1 -1
  3. package/dist/manifest.json +2 -2
  4. package/dist/models/Message.d.ts +10 -0
  5. package/dist/models/Message.d.ts.map +1 -1
  6. package/dist/smrt-knowledge.json +4 -4
  7. package/dist/svelte/components/AccountAvatar.svelte +4 -4
  8. package/dist/svelte/components/AccountList.svelte +12 -3
  9. package/dist/svelte/components/AccountList.svelte.d.ts.map +1 -1
  10. package/dist/svelte/components/ComposeForm.svelte +27 -2
  11. package/dist/svelte/components/ComposeForm.svelte.d.ts.map +1 -1
  12. package/dist/svelte/components/EmailAccountManager.svelte +25 -2
  13. package/dist/svelte/components/EmailAccountManager.svelte.d.ts.map +1 -1
  14. package/dist/svelte/components/EmailFilterManager.svelte +50 -2
  15. package/dist/svelte/components/EmailFilterManager.svelte.d.ts.map +1 -1
  16. package/dist/svelte/components/ForwardForm.svelte +26 -2
  17. package/dist/svelte/components/ForwardForm.svelte.d.ts.map +1 -1
  18. package/dist/svelte/components/MessageDetail.svelte +11 -14
  19. package/dist/svelte/components/MessageDetail.svelte.d.ts.map +1 -1
  20. package/dist/svelte/components/ReplyForm.svelte +26 -2
  21. package/dist/svelte/components/ReplyForm.svelte.d.ts.map +1 -1
  22. package/dist/svelte/components/SendStatusBadge.svelte +1 -1
  23. package/dist/svelte/components/ThreadView.svelte +10 -3
  24. package/dist/svelte/components/ThreadView.svelte.d.ts.map +1 -1
  25. package/dist/svelte/i18n.d.ts +2 -0
  26. package/dist/svelte/i18n.d.ts.map +1 -1
  27. package/dist/svelte/i18n.js +2 -0
  28. package/package.json +7 -7
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "version": "1.0.0",
3
- "timestamp": 1782183781608,
3
+ "timestamp": 1782192569825,
4
4
  "packageName": "@happyvertical/smrt-messages",
5
- "packageVersion": "0.31.0",
5
+ "packageVersion": "0.31.1",
6
6
  "objects": {
7
7
  "@happyvertical/smrt-messages:AccountCollection": {
8
8
  "name": "accountcollection",
@@ -87,6 +87,16 @@ export declare class Message extends SmrtObject {
87
87
  * Retry sending a failed message
88
88
  */
89
89
  retrySend(options?: SendMessageOptions): Promise<MessageSendResult>;
90
+ /**
91
+ * Options for a derived draft (reply/forward) built from this message. Carries
92
+ * the DB connection + tenant context from `this.options`, but strips this
93
+ * message's own identity fields. When this message was hydrated from the DB,
94
+ * `this.options` holds the row's `id`/`slug`/`context`/`_skipLoad`; spreading
95
+ * those into a new draft would make `draft.save()` upsert onto the natural-key
96
+ * conflict columns (`slug`/`context`/`_meta_type`) and overwrite the ORIGINAL
97
+ * message instead of inserting a new row. See EmailAccount.childOptions().
98
+ */
99
+ protected draftOptions(): Record<string, unknown>;
90
100
  /**
91
101
  * Create a reply to this message (returns unsaved draft)
92
102
  */
@@ -1 +1 @@
1
- {"version":3,"file":"Message.d.ts","sourceRoot":"","sources":["../../src/models/Message.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAc,UAAU,EAAQ,MAAM,0BAA0B,CAAC;AAExE,OAAO,KAAK,EACV,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,UAAU,EACX,MAAM,UAAU,CAAC;AAElB,qBAOa,OAAQ,SAAQ,UAAU;IAErC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IAG/B,SAAS,SAAM;IACf,QAAQ,SAAM;IACd,OAAO,SAAM;IACb,IAAI,SAAM;IACV,WAAW,SAAM;IACjB,QAAQ,SAAM;IACd,WAAW,SAAM;IACjB,IAAI,EAAE,IAAI,GAAG,IAAI,CAAQ;IACzB,MAAM,UAAS;IACf,SAAS,UAAS;IAClB,cAAc,UAAS;IACvB,IAAI,SAAK;IACT,QAAQ,SAAM;IAGd,UAAU,EAAE,UAAU,CAAW;IACjC,MAAM,EAAE,IAAI,GAAG,IAAI,CAAQ;IAC3B,SAAS,SAAM;IACf,UAAU,SAAK;IACf,UAAU,SAAK;IACf,eAAe,EAAE,IAAI,GAAG,IAAI,CAAQ;IAEpC,kBAAkB,SAAM;IAGxB,SAAS,OAAc;IACvB,SAAS,OAAc;gBAEX,OAAO,GAAE,cAAmB;IAiCxC;;OAEG;IACH,cAAc,IAAI,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAS3D;;OAEG;IACH,cAAc,CAAC,SAAS,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,IAAI;IAI1E;;OAEG;IACH,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IASlC;;OAEG;IACH,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI;IAI5C;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAM/B;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAMjC;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAMpC;;OAEG;IACH,QAAQ,IAAI,OAAO;IAInB;;OAEG;IACH,UAAU,CAAC,SAAS,SAAM,GAAG,MAAM;IAMnC;;OAEG;IACG,UAAU;IAWhB;;OAEG;IACG,iBAAiB,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAW7C;;OAEG;IACG,cAAc;IAapB;;OAEG;IACG,IAAI,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAuDpE;;OAEG;IACG,SAAS,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAwBzE;;OAEG;IACH,WAAW,CAAC,QAAQ,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO;IA0BvD;;OAEG;IACH,aAAa,IAAI,OAAO;IAyBxB;;OAEG;IACH,SAAS,CAAC,eAAe,IAAI,MAAM;CAapC"}
1
+ {"version":3,"file":"Message.d.ts","sourceRoot":"","sources":["../../src/models/Message.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAc,UAAU,EAAQ,MAAM,0BAA0B,CAAC;AAExE,OAAO,KAAK,EACV,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,UAAU,EACX,MAAM,UAAU,CAAC;AAElB,qBAOa,OAAQ,SAAQ,UAAU;IAErC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IAG/B,SAAS,SAAM;IACf,QAAQ,SAAM;IACd,OAAO,SAAM;IACb,IAAI,SAAM;IACV,WAAW,SAAM;IACjB,QAAQ,SAAM;IACd,WAAW,SAAM;IACjB,IAAI,EAAE,IAAI,GAAG,IAAI,CAAQ;IACzB,MAAM,UAAS;IACf,SAAS,UAAS;IAClB,cAAc,UAAS;IACvB,IAAI,SAAK;IACT,QAAQ,SAAM;IAGd,UAAU,EAAE,UAAU,CAAW;IACjC,MAAM,EAAE,IAAI,GAAG,IAAI,CAAQ;IAC3B,SAAS,SAAM;IACf,UAAU,SAAK;IACf,UAAU,SAAK;IACf,eAAe,EAAE,IAAI,GAAG,IAAI,CAAQ;IAEpC,kBAAkB,SAAM;IAGxB,SAAS,OAAc;IACvB,SAAS,OAAc;gBAEX,OAAO,GAAE,cAAmB;IAiCxC;;OAEG;IACH,cAAc,IAAI,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAS3D;;OAEG;IACH,cAAc,CAAC,SAAS,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,IAAI;IAI1E;;OAEG;IACH,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IASlC;;OAEG;IACH,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI;IAI5C;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAM/B;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAMjC;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAMpC;;OAEG;IACH,QAAQ,IAAI,OAAO;IAInB;;OAEG;IACH,UAAU,CAAC,SAAS,SAAM,GAAG,MAAM;IAMnC;;OAEG;IACG,UAAU;IAWhB;;OAEG;IACG,iBAAiB,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAW7C;;OAEG;IACG,cAAc;IAapB;;OAEG;IACG,IAAI,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAwFpE;;OAEG;IACG,SAAS,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAwBzE;;;;;;;;OAQG;IACH,SAAS,CAAC,YAAY,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IASjD;;OAEG;IACH,WAAW,CAAC,QAAQ,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO;IA0BvD;;OAEG;IACH,aAAa,IAAI,OAAO;IAyBxB;;OAEG;IACH,SAAS,CAAC,eAAe,IAAI,MAAM;CAapC"}
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-06-23T03:03:02.228Z",
3
+ "generatedAt": "2026-06-23T05:29:30.369Z",
4
4
  "packageName": "@happyvertical/smrt-messages",
5
- "packageVersion": "0.31.0",
5
+ "packageVersion": "0.31.1",
6
6
  "sourceManifestPath": "dist/manifest.json",
7
7
  "agentDocPath": "AGENTS.md",
8
8
  "sourceHashes": {
9
- "manifest": "24fabbc50c373532579aca4ef41d252920ebfb3578c203a629e66deba5e77496",
10
- "packageJson": "4d2d75da2257ab444faa4783d4924cff447410a0620f0c76b106c9dbef445952",
9
+ "manifest": "1f6a120e0283bad9c9fdc24c484859aed4a96ad5f95251652726be0b89efd677",
10
+ "packageJson": "0472121595e3074e12c90af955583d88bde78684290e5af4851d10259a0b3804",
11
11
  "agents": "32563a30ceeb21bd8732042f894e2a148fd83727caffc1eec7c97540005c6fdc"
12
12
  },
13
13
  "exports": [
@@ -87,13 +87,13 @@ const _icon = $derived.by(() => {
87
87
  }
88
88
 
89
89
  .avatar--slack {
90
- background: #e8def8;
91
- color: #4a1175;
90
+ background: var(--smrt-color-tertiary-container, #e8def8);
91
+ color: var(--smrt-color-on-tertiary-container, #4a1175);
92
92
  }
93
93
 
94
94
  .avatar--twitter {
95
- background: #d3e8fd;
96
- color: #0c4a6e;
95
+ background: var(--smrt-color-secondary-container, #d3e8fd);
96
+ color: var(--smrt-color-on-secondary-container, #0c4a6e);
97
97
  }
98
98
 
99
99
  .icon {
@@ -26,7 +26,16 @@ const {
26
26
  onremove,
27
27
  }: Props = $props();
28
28
 
29
- function handleAccountClick(account: AccountData) {
29
+ function handleAccountClick(event: MouseEvent, account: AccountData) {
30
+ // Action buttons (Sync/Activate/Deactivate/Remove) render inside the
31
+ // AccountCard, which sits inside this role="button" wrapper. A click on one
32
+ // of them bubbles up here and would also fire onaccountclick. Ignore clicks
33
+ // that originate inside an interactive control so only the card surface
34
+ // itself opens the account.
35
+ const target = event.target as HTMLElement | null;
36
+ if (target?.closest('button, a, input, select, textarea')) {
37
+ return;
38
+ }
30
39
  onaccountclick?.(account);
31
40
  }
32
41
 
@@ -37,7 +46,7 @@ function handleAccountKeydown(event: KeyboardEvent, account: AccountData) {
37
46
 
38
47
  if (event.key === 'Enter' || event.key === ' ') {
39
48
  event.preventDefault();
40
- handleAccountClick(account);
49
+ onaccountclick?.(account);
41
50
  }
42
51
  }
43
52
  </script>
@@ -59,7 +68,7 @@ function handleAccountKeydown(event: KeyboardEvent, account: AccountData) {
59
68
  <div
60
69
  role="button"
61
70
  tabindex="0"
62
- onclick={() => handleAccountClick(account)}
71
+ onclick={(event) => handleAccountClick(event, account)}
63
72
  onkeydown={(event) => handleAccountKeydown(event, account)}
64
73
  >
65
74
  <AccountCard {account} {onsync} {onremove} />
@@ -1 +1 @@
1
- {"version":3,"file":"AccountList.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/AccountList.svelte.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C,MAAM,WAAW,KAAK;IACpB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IAChD,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IACxC,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;CAC3C;AAoED,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"AccountList.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/AccountList.svelte.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C,MAAM,WAAW,KAAK;IACpB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IAChD,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IACxC,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;CAC3C;AA6ED,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -61,6 +61,7 @@ export interface Props {
61
61
  let channelId = $state('');
62
62
  let isDirty = $state(false);
63
63
  let isSending = $state(false);
64
+ let sendError = $state<string | null>(null);
64
65
  let showCc = $state(false);
65
66
  let showBcc = $state(false);
66
67
  let appliedInitialState: Partial<ComposeState> | undefined;
@@ -88,6 +89,7 @@ export interface Props {
88
89
  channelId = nextState.channelId ?? '';
89
90
  isDirty = nextState.isDirty;
90
91
  isSending = nextState.isSending;
92
+ sendError = null;
91
93
  showCc = ccRecipients.length > 0;
92
94
  showBcc = bccRecipients.length > 0;
93
95
  });
@@ -114,11 +116,20 @@ export interface Props {
114
116
  isDirty = true;
115
117
  }
116
118
 
117
- function handleSend() {
119
+ async function handleSend() {
118
120
  if (isSending) return;
119
121
 
120
122
  isSending = true;
121
- onsend?.(getCurrentState());
123
+ sendError = null;
124
+ try {
125
+ // onsend may be sync or async; awaiting handles both a thrown error and a
126
+ // rejected promise so the form never latches in the "Sending…" state.
127
+ await onsend?.(getCurrentState());
128
+ } catch (e) {
129
+ sendError = e instanceof Error ? e.message : String(e);
130
+ } finally {
131
+ isSending = false;
132
+ }
122
133
  }
123
134
 
124
135
  function handleSaveDraft() {
@@ -241,6 +252,12 @@ export interface Props {
241
252
  />
242
253
  {/if}
243
254
 
255
+ {#if sendError}
256
+ <div class="send-error" role="alert" aria-live="assertive">
257
+ {sendError}
258
+ </div>
259
+ {/if}
260
+
244
261
  <div class="actions">
245
262
  <button
246
263
  type="submit"
@@ -342,6 +359,14 @@ export interface Props {
342
359
  font-weight: var(--smrt-typography-weight-semibold, 600);
343
360
  }
344
361
 
362
+ .send-error {
363
+ padding: var(--smrt-spacing-2, 8px) var(--smrt-spacing-3, 12px);
364
+ border-radius: var(--smrt-radius-sm, 8px);
365
+ background: var(--smrt-color-error-container, #ffdad6);
366
+ color: var(--smrt-color-on-error-container, #410002);
367
+ font-size: var(--smrt-typography-body-small-size, 12px);
368
+ }
369
+
345
370
  .actions {
346
371
  display: flex;
347
372
  gap: var(--smrt-spacing-2, 8px);
@@ -1 +1 @@
1
- {"version":3,"file":"ComposeForm.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/ComposeForm.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,WAAW,EAEX,YAAY,EAEb,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,KAAK;IACpB,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;IACnC,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;IACzB,YAAY,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IACrC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IACvC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IAC5C,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAmND,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"ComposeForm.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/ComposeForm.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,WAAW,EAEX,YAAY,EAEb,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,KAAK;IACpB,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;IACnC,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;IACzB,YAAY,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IACrC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IACvC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IAC5C,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAoOD,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -34,6 +34,7 @@ let editingId = $state<string | null>(null);
34
34
  let saving = $state(false);
35
35
  let testingId = $state<string | null>(null);
36
36
  let testResult = $state<{ success: boolean; error?: string } | null>(null);
37
+ let actionError = $state<string | null>(null);
37
38
 
38
39
  // Form state
39
40
  let maName = $state('');
@@ -65,6 +66,7 @@ function resetForm() {
65
66
  editingId = null;
66
67
  showForm = false;
67
68
  testResult = null;
69
+ actionError = null;
68
70
  }
69
71
 
70
72
  function startEdit(acct: EmailAccountData) {
@@ -80,16 +82,21 @@ function startEdit(acct: EmailAccountData) {
80
82
  maSmtpPort = acct.smtpPort ?? 465;
81
83
  maSmtpSecurity = acct.smtpSecurity ?? 'ssl';
82
84
  maUsername = acct.username ?? '';
83
- maPassword = acct.password ?? '';
85
+ // Never echo the stored secret into the DOM. Leave the password blank on edit
86
+ // ('(unchanged)' placeholder); save() only writes a new password when one is
87
+ // typed (see the `!editingId || maPassword` guard).
88
+ maPassword = '';
84
89
  editingId = acct.id;
85
90
  showForm = true;
86
91
  testResult = null;
92
+ actionError = null;
87
93
  }
88
94
 
89
95
  async function save() {
90
96
  if (!maName.trim() || !maEmail.trim() || !onsave) return;
91
97
  try {
92
98
  saving = true;
99
+ actionError = null;
93
100
  const data: Partial<EmailAccountData> = {
94
101
  name: maName.trim(),
95
102
  email: maEmail.trim(),
@@ -109,6 +116,7 @@ async function save() {
109
116
  await onsave(data, editingId ?? undefined);
110
117
  resetForm();
111
118
  } catch (e) {
119
+ actionError = e instanceof Error ? e.message : String(e);
112
120
  } finally {
113
121
  saving = false;
114
122
  }
@@ -117,9 +125,12 @@ async function save() {
117
125
  async function remove(acct: EmailAccountData) {
118
126
  if (isReadonly || !ondelete) return;
119
127
  try {
128
+ actionError = null;
120
129
  await ondelete(acct);
121
130
  if (editingId === acct.id) resetForm();
122
- } catch (e) {}
131
+ } catch (e) {
132
+ actionError = e instanceof Error ? e.message : String(e);
133
+ }
123
134
  }
124
135
 
125
136
  async function testConnection(acct: EmailAccountData) {
@@ -258,6 +269,18 @@ function getProviderLabel(type: string): string {
258
269
  </div>
259
270
  {/if}
260
271
 
272
+ {#if actionError}
273
+ <div class="test-result failure" role="alert" aria-live="assertive">
274
+ {actionError}
275
+ <button
276
+ type="button"
277
+ class="dismiss-btn"
278
+ aria-label={t(M['messages.email_account_manager.dismiss_error'])}
279
+ onclick={() => actionError = null}
280
+ >&times;</button>
281
+ </div>
282
+ {/if}
283
+
261
284
  {#if testResult}
262
285
  <div class="test-result" class:success={testResult.success} class:failure={!testResult.success}>
263
286
  {#if testResult.success}
@@ -1 +1 @@
1
- {"version":3,"file":"EmailAccountManager.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/EmailAccountManager.svelte.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGpD,MAAM,WAAW,KAAK;IACpB,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,gBAAgB,CAAC,EAAE,EAAE,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzE,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,MAAM,CAAC,EAAE,CACP,OAAO,EAAE,gBAAgB,KACtB,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpD;AA6SD,QAAA,MAAM,mBAAmB,2CAAwC,CAAC;AAClE,KAAK,mBAAmB,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAClE,eAAe,mBAAmB,CAAC"}
1
+ {"version":3,"file":"EmailAccountManager.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/EmailAccountManager.svelte.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGpD,MAAM,WAAW,KAAK;IACpB,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,gBAAgB,CAAC,EAAE,EAAE,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzE,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,MAAM,CAAC,EAAE,CACP,OAAO,EAAE,gBAAgB,KACtB,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpD;AA+TD,QAAA,MAAM,mBAAmB,2CAAwC,CAAC;AAClE,KAAK,mBAAmB,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAClE,eAAe,mBAAmB,CAAC"}
@@ -33,6 +33,11 @@ const {
33
33
 
34
34
  type ActiveSection = 'whitelist' | 'blacklist';
35
35
  let activeSection = $state<ActiveSection>('whitelist');
36
+ let filterError = $state<string | null>(null);
37
+
38
+ function toErrorMessage(e: unknown): string {
39
+ return e instanceof Error ? e.message : String(e);
40
+ }
36
41
 
37
42
  // Whitelist form state
38
43
  let showWhitelistForm = $state(false);
@@ -70,6 +75,7 @@ async function saveWhitelistEntry() {
70
75
  if (!wlPattern.trim() || !onaddwhitelist) return;
71
76
  try {
72
77
  savingWhitelist = true;
78
+ filterError = null;
73
79
  await onaddwhitelist({
74
80
  pattern: wlPattern.trim(),
75
81
  type: wlType,
@@ -78,6 +84,7 @@ async function saveWhitelistEntry() {
78
84
  });
79
85
  resetWhitelistForm();
80
86
  } catch (e) {
87
+ filterError = toErrorMessage(e);
81
88
  } finally {
82
89
  savingWhitelist = false;
83
90
  }
@@ -86,14 +93,18 @@ async function saveWhitelistEntry() {
86
93
  async function removeWhitelistEntry(entry: WhitelistEntry) {
87
94
  if (isReadonly || !onremovewhitelist) return;
88
95
  try {
96
+ filterError = null;
89
97
  await onremovewhitelist(entry);
90
- } catch (e) {}
98
+ } catch (e) {
99
+ filterError = toErrorMessage(e);
100
+ }
91
101
  }
92
102
 
93
103
  async function saveBlacklistEntry() {
94
104
  if (!blPattern.trim() || !onaddblacklist) return;
95
105
  try {
96
106
  savingBlacklist = true;
107
+ filterError = null;
97
108
  await onaddblacklist({
98
109
  pattern: blPattern.trim(),
99
110
  type: blType,
@@ -102,6 +113,7 @@ async function saveBlacklistEntry() {
102
113
  });
103
114
  resetBlacklistForm();
104
115
  } catch (e) {
116
+ filterError = toErrorMessage(e);
105
117
  } finally {
106
118
  savingBlacklist = false;
107
119
  }
@@ -110,8 +122,11 @@ async function saveBlacklistEntry() {
110
122
  async function removeBlacklistEntry(entry: BlacklistEntry) {
111
123
  if (isReadonly || !onremoveblacklist) return;
112
124
  try {
125
+ filterError = null;
113
126
  await onremoveblacklist(entry);
114
- } catch (e) {}
127
+ } catch (e) {
128
+ filterError = toErrorMessage(e);
129
+ }
115
130
  }
116
131
 
117
132
  function getTypeIcon(type: string): string {
@@ -161,6 +176,18 @@ function getPatternPlaceholder(type: string): string {
161
176
  </button>
162
177
  </div>
163
178
 
179
+ {#if filterError}
180
+ <div class="filter-error" role="alert" aria-live="assertive">
181
+ {filterError}
182
+ <button
183
+ type="button"
184
+ class="dismiss-btn"
185
+ aria-label={t(M['messages.email_filter_manager.dismiss_error'])}
186
+ onclick={() => filterError = null}
187
+ >&times;</button>
188
+ </div>
189
+ {/if}
190
+
164
191
  <!-- Whitelist Section -->
165
192
  {#if activeSection === 'whitelist'}
166
193
  <div class="section-content">
@@ -684,4 +711,25 @@ function getPatternPlaceholder(type: string): string {
684
711
  background: var(--smrt-color-surface-container, #f0f1f9);
685
712
  border-radius: var(--smrt-radius-md, 8px);
686
713
  }
714
+
715
+ .filter-error {
716
+ display: flex;
717
+ align-items: center;
718
+ justify-content: space-between;
719
+ gap: 0.5rem;
720
+ padding: 0.5rem 0.75rem;
721
+ border-radius: var(--smrt-radius-md, 8px);
722
+ background: var(--smrt-color-error-container, #fce4ec);
723
+ color: var(--smrt-color-error, #ba1a1a);
724
+ font-size: var(--smrt-typography-body-medium-size, 0.8125rem);
725
+ }
726
+
727
+ .dismiss-btn {
728
+ background: transparent;
729
+ border: none;
730
+ font-size: var(--smrt-typography-body-large-size, 1rem);
731
+ cursor: pointer;
732
+ color: inherit;
733
+ padding: 0 0.25rem;
734
+ }
687
735
  </style>
@@ -1 +1 @@
1
- {"version":3,"file":"EmailFilterManager.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/EmailFilterManager.svelte.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlE,MAAM,WAAW,KAAK;IACpB,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrE,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrE,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9D;AA6SD,QAAA,MAAM,kBAAkB,2CAAwC,CAAC;AACjE,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAChE,eAAe,kBAAkB,CAAC"}
1
+ {"version":3,"file":"EmailFilterManager.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/EmailFilterManager.svelte.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlE,MAAM,WAAW,KAAK;IACpB,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrE,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrE,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9D;AAmUD,QAAA,MAAM,kBAAkB,2CAAwC,CAAC;AACjE,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAChE,eAAe,kBAAkB,CAAC"}
@@ -20,6 +20,7 @@ export interface Props {
20
20
  let to: RecipientEntry[] = $state([]);
21
21
  let body = $state('');
22
22
  let isSending = $state(false);
23
+ let sendError = $state<string | null>(null);
23
24
 
24
25
  const forwardedBlock = $derived.by(() => {
25
26
  const dateStr = originalMessage.date
@@ -41,10 +42,19 @@ export interface Props {
41
42
  ].join('\n');
42
43
  });
43
44
 
44
- function handleSend() {
45
+ async function handleSend() {
45
46
  if (isSending || to.length === 0) return;
46
47
  isSending = true;
47
- onsend?.(to, body);
48
+ sendError = null;
49
+ try {
50
+ // onsend may be sync or async; awaiting handles both so the button never
51
+ // latches in the "Sending…" state on failure.
52
+ await onsend?.(to, body);
53
+ } catch (e) {
54
+ sendError = e instanceof Error ? e.message : String(e);
55
+ } finally {
56
+ isSending = false;
57
+ }
48
58
  }
49
59
  </script>
50
60
 
@@ -68,6 +78,12 @@ export interface Props {
68
78
  <pre class="forwarded-text">{forwardedBlock}</pre>
69
79
  </div>
70
80
 
81
+ {#if sendError}
82
+ <div class="send-error" role="alert" aria-live="assertive">
83
+ {sendError}
84
+ </div>
85
+ {/if}
86
+
71
87
  <div class="actions">
72
88
  <button
73
89
  type="button"
@@ -133,6 +149,14 @@ export interface Props {
133
149
  font-family: var(--smrt-font-family, system-ui);
134
150
  }
135
151
 
152
+ .send-error {
153
+ padding: var(--smrt-spacing-2, 8px) var(--smrt-spacing-3, 12px);
154
+ border-radius: var(--smrt-radius-sm, 8px);
155
+ background: var(--smrt-color-error-container, #ffdad6);
156
+ color: var(--smrt-color-on-error-container, #410002);
157
+ font-size: var(--smrt-typography-body-small-size, 12px);
158
+ }
159
+
136
160
  .actions {
137
161
  display: flex;
138
162
  gap: var(--smrt-spacing-2, 8px);
@@ -1 +1 @@
1
- {"version":3,"file":"ForwardForm.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/ForwardForm.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE/D,MAAM,WAAW,KAAK;IACpB,eAAe,EAAE,WAAW,CAAC;IAC7B,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,cAAc,EAAE,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACtD,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AA0ED,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"ForwardForm.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/ForwardForm.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE/D,MAAM,WAAW,KAAK;IACpB,eAAe,EAAE,WAAW,CAAC;IAC7B,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,cAAc,EAAE,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACtD,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AA0FD,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -69,15 +69,16 @@ const _slackMeta = $derived.by(() => {
69
69
  return message.meta || {};
70
70
  });
71
71
 
72
- const _sanitizedHtml = $derived.by(() => {
73
- if (!message.htmlBody) return '';
74
- return message.htmlBody
75
- .replace(/<script[\s\S]*?<\/script>/gi, '')
76
- .replace(/<iframe[\s\S]*?<\/iframe>/gi, '')
77
- .replace(/\son\w+\s*=\s*("[^"]*"|'[^']*'|[^\s>]*)/gi, '')
78
- .replace(/javascript\s*:/gi, 'blocked:')
79
- .replace(/<link[^>]*>/gi, '')
80
- .replace(/<style[\s\S]*?<\/style>/gi, '');
72
+ // Body is always rendered as untrusted plain text — we never inject provider
73
+ // HTML via {@html}. A regex "sanitizer" is bypassable (e.g. an unclosed
74
+ // <script> survives), so when the caller opts into the HTML body we down-render
75
+ // it to text by stripping tags. Real rich-HTML rendering, if ever needed, must
76
+ // go through a vetted sanitizer or a sandboxed iframe (out of scope here).
77
+ const _displayBody = $derived.by(() => {
78
+ if (showHtml && message.htmlBody) {
79
+ return message.htmlBody.replace(/<[^>]*>/g, '');
80
+ }
81
+ return message.body;
81
82
  });
82
83
  </script>
83
84
 
@@ -144,11 +145,7 @@ const _sanitizedHtml = $derived.by(() => {
144
145
  {/if}
145
146
 
146
147
  <div class="body">
147
- {#if showHtml && message.htmlBody}
148
- {@html _sanitizedHtml}
149
- {:else}
150
- <pre class="body-text">{message.body}</pre>
151
- {/if}
148
+ <pre class="body-text">{_displayBody}</pre>
152
149
  </div>
153
150
 
154
151
  {#if attachments.length > 0}
@@ -1 +1 @@
1
- {"version":3,"file":"MessageDetail.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/MessageDetail.svelte.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAM5E,MAAM,WAAW,KAAK;IACpB,OAAO,EAAE,WAAW,CAAC;IACrB,WAAW,CAAC,EAAE,cAAc,EAAE,CAAC;IAC/B,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IACzC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IAC3C,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IAC1C,UAAU,CAAC,EAAE,OAAO,CAAC,CAAC;QAAE,OAAO,EAAE,WAAW,CAAA;KAAE,CAAC,CAAC,CAAC;CAClD;AA+KD,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"MessageDetail.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/MessageDetail.svelte.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAM5E,MAAM,WAAW,KAAK;IACpB,OAAO,EAAE,WAAW,CAAC;IACrB,WAAW,CAAC,EAAE,cAAc,EAAE,CAAC;IAC/B,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IACzC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IAC3C,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IAC1C,UAAU,CAAC,EAAE,OAAO,CAAC,CAAC;QAAE,OAAO,EAAE,WAAW,CAAA;KAAE,CAAC,CAAC,CAAC;CAClD;AA4KD,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -24,6 +24,7 @@ export interface Props {
24
24
 
25
25
  let body = $state('');
26
26
  let isSending = $state(false);
27
+ let sendError = $state<string | null>(null);
27
28
 
28
29
  const quotedBody = $derived.by(() => {
29
30
  const dateStr = originalMessage.date
@@ -37,10 +38,19 @@ export interface Props {
37
38
  return `On ${dateStr}, ${from} wrote:\n${lines}`;
38
39
  });
39
40
 
40
- function handleSend() {
41
+ async function handleSend() {
41
42
  if (isSending) return;
42
43
  isSending = true;
43
- onsend?.(body);
44
+ sendError = null;
45
+ try {
46
+ // onsend may be sync or async; awaiting handles both so the button never
47
+ // latches in the "Sending…" state on failure.
48
+ await onsend?.(body);
49
+ } catch (e) {
50
+ sendError = e instanceof Error ? e.message : String(e);
51
+ } finally {
52
+ isSending = false;
53
+ }
44
54
  }
45
55
  </script>
46
56
 
@@ -60,6 +70,12 @@ export interface Props {
60
70
  <pre class="quoted-text">{quotedBody}</pre>
61
71
  </div>
62
72
 
73
+ {#if sendError}
74
+ <div class="send-error" role="alert" aria-live="assertive">
75
+ {sendError}
76
+ </div>
77
+ {/if}
78
+
63
79
  <div class="actions">
64
80
  <button
65
81
  type="button"
@@ -126,6 +142,14 @@ export interface Props {
126
142
  font-family: var(--smrt-font-family, system-ui);
127
143
  }
128
144
 
145
+ .send-error {
146
+ padding: var(--smrt-spacing-2, 8px) var(--smrt-spacing-3, 12px);
147
+ border-radius: var(--smrt-radius-sm, 8px);
148
+ background: var(--smrt-color-error-container, #ffdad6);
149
+ color: var(--smrt-color-on-error-container, #410002);
150
+ font-size: var(--smrt-typography-body-small-size, 12px);
151
+ }
152
+
129
153
  .actions {
130
154
  display: flex;
131
155
  gap: var(--smrt-spacing-2, 8px);
@@ -1 +1 @@
1
- {"version":3,"file":"ReplyForm.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/ReplyForm.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,MAAM,WAAW,KAAK;IACpB,eAAe,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAoED,QAAA,MAAM,SAAS,2CAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"ReplyForm.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/ReplyForm.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,MAAM,WAAW,KAAK;IACpB,eAAe,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAoFD,QAAA,MAAM,SAAS,2CAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
@@ -17,7 +17,7 @@ export interface Props {
17
17
  case 'sending':
18
18
  return { label: 'Sending', color: 'var(--smrt-color-primary, #6750a4)', icon: '↑' };
19
19
  case 'sent':
20
- return { label: 'Sent', color: 'var(--smrt-color-primary, #006d3b)', icon: '✓' };
20
+ return { label: 'Sent', color: 'var(--smrt-color-success, #006d3b)', icon: '✓' };
21
21
  case 'failed':
22
22
  return { label: 'Failed', color: 'var(--smrt-color-error, #ba1a1a)', icon: '✕' };
23
23
  case 'scheduled':
@@ -46,7 +46,14 @@ $effect(() => {
46
46
  collapsed = new Set(initialCollapsed);
47
47
  });
48
48
 
49
- function handleMessageClick(message: MessageData) {
49
+ function handleMessageClick(event: MouseEvent, message: MessageData) {
50
+ // MessageDetail renders its own Reply/Forward/Delete buttons inside this
51
+ // role="button" wrapper. Ignore clicks that originate inside an interactive
52
+ // control so only the message surface itself fires onmessageclick.
53
+ const target = event.target as HTMLElement | null;
54
+ if (target?.closest('button, a, input, select, textarea')) {
55
+ return;
56
+ }
50
57
  onmessageclick?.(message);
51
58
  }
52
59
 
@@ -57,7 +64,7 @@ function handleMessageKeydown(event: KeyboardEvent, message: MessageData) {
57
64
 
58
65
  if (event.key === 'Enter' || event.key === ' ') {
59
66
  event.preventDefault();
60
- handleMessageClick(message);
67
+ onmessageclick?.(message);
61
68
  }
62
69
  }
63
70
 
@@ -128,7 +135,7 @@ const _sortedMessages = $derived(
128
135
  <div
129
136
  role="button"
130
137
  tabindex="0"
131
- onclick={() => handleMessageClick(message)}
138
+ onclick={(event) => handleMessageClick(event, message)}
132
139
  onkeydown={(event) => handleMessageKeydown(event, message)}
133
140
  >
134
141
  <MessageDetail
@@ -1 +1 @@
1
- {"version":3,"file":"ThreadView.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/ThreadView.svelte.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C,MAAM,WAAW,KAAK;IACpB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC/B,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IAChD,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;CAC1C;AA8HD,QAAA,MAAM,UAAU,2CAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"ThreadView.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/ThreadView.svelte.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C,MAAM,WAAW,KAAK;IACpB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC/B,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IAChD,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;CAC1C;AAqID,QAAA,MAAM,UAAU,2CAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
@@ -24,12 +24,14 @@ export declare const M: {
24
24
  readonly 'messages.email_account_manager.imap_host_title': "messages.email_account_manager.imap_host_title";
25
25
  readonly 'messages.email_account_manager.test_connection': "messages.email_account_manager.test_connection";
26
26
  readonly 'messages.email_account_manager.remove': "messages.email_account_manager.remove";
27
+ readonly 'messages.email_account_manager.dismiss_error': "messages.email_account_manager.dismiss_error";
27
28
  readonly 'messages.email_filter_manager.whitelist_description': "messages.email_filter_manager.whitelist_description";
28
29
  readonly 'messages.email_filter_manager.add_whitelist_entry': "messages.email_filter_manager.add_whitelist_entry";
29
30
  readonly 'messages.email_filter_manager.category_placeholder': "messages.email_filter_manager.category_placeholder";
30
31
  readonly 'messages.email_filter_manager.whitelist_description_placeholder': "messages.email_filter_manager.whitelist_description_placeholder";
31
32
  readonly 'messages.email_filter_manager.no_whitelist_entries': "messages.email_filter_manager.no_whitelist_entries";
32
33
  readonly 'messages.email_filter_manager.whitelist_remove': "messages.email_filter_manager.whitelist_remove";
34
+ readonly 'messages.email_filter_manager.dismiss_error': "messages.email_filter_manager.dismiss_error";
33
35
  readonly 'messages.email_filter_manager.blacklist_description': "messages.email_filter_manager.blacklist_description";
34
36
  readonly 'messages.email_filter_manager.add_blacklist_entry': "messages.email_filter_manager.add_blacklist_entry";
35
37
  readonly 'messages.email_filter_manager.reason_placeholder': "messages.email_filter_manager.reason_placeholder";
@@ -1 +1 @@
1
- {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../src/svelte/i18n.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8DZ,CAAC"}
1
+ {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../src/svelte/i18n.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgEZ,CAAC"}