@zaclinley/dsh-mailharbor 0.5.1 → 0.7.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.
package/lib/client.js CHANGED
@@ -35,7 +35,7 @@ __export(index_exports, {
35
35
  module.exports = __toCommonJS(index_exports);
36
36
 
37
37
  // src/client/styles.css
38
- var styles_default = "/* MailHarbor panel + settings.\n Tokens come from the DSH shell (dark by default); fallbacks match the\n default dark theme so the panel also renders in a bare host page.\n Component rules are all scoped under `.mailharbor` and the generic element\n reset lives in `:where()` so it cannot outrank a component class. */\n\n.mailharbor {\n --m-bg: var(--dsw-alias-bg-base, #151517);\n --m-bg-1: var(--dsw-alias-bg-layer-1, #232324);\n --m-bg-2: var(--dsw-alias-bg-layer-2, #2c2c2e);\n --m-bg-3: var(--dsw-alias-bg-layer-3, #353638);\n --m-line: var(--dsw-alias-border-l2, #ffffff1f);\n --m-line-soft: var(--dsw-alias-border-l1, #ffffff0f);\n --m-line-strong: var(--dsw-alias-border-l3, #ffffff29);\n --m-text: var(--dsw-alias-label-primary, #f9fafb);\n --m-text-2: var(--dsw-alias-label-secondary, #cfd3d6);\n --m-text-3: var(--dsw-alias-label-tertiary, #adb2b8);\n --m-caption: var(--dsw-alias-label-caption, #81858c);\n --m-accent: #5686fe;\n --m-accent-soft: rgba(86, 134, 254, 0.14);\n --m-primary-bg: var(--dsw-alias-brand-primary, #f9fafb);\n --m-primary-fg: var(--dsw-alias-label-primary-foreground, #0f1115);\n --m-hover: var(--dsw-alias-interactive-bg-hover, #ffffff14);\n --m-danger: #f25a5a;\n --m-ok: #3fbf7f;\n --m-warn: var(--dsw-alias-state-warn-label, #dd8629);\n --m-radius: 8px;\n --m-shadow: 0 18px 60px rgba(0, 0, 0, 0.5);\n\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n background: var(--m-bg);\n color: var(--m-text);\n font-size: 13px;\n line-height: 1.5;\n -webkit-font-smoothing: antialiased;\n}\n\n.mailharbor *,\n.mailharbor *::before,\n.mailharbor *::after {\n box-sizing: border-box;\n}\n\n/* ---------- base controls ---------- */\n\n.mailharbor :where(button) {\n font: inherit;\n color: inherit;\n background: transparent;\n border: 0;\n margin: 0;\n cursor: pointer;\n}\n\n.mailharbor button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n height: 30px;\n padding: 0 12px;\n border-radius: var(--m-radius);\n border: 1px solid transparent;\n color: var(--m-text-2);\n font-size: 12.5px;\n font-weight: 500;\n white-space: nowrap;\n transition:\n background 120ms ease,\n color 120ms ease,\n border-color 120ms ease;\n}\n\n.mailharbor button:hover:not(:disabled) {\n background: var(--m-hover);\n color: var(--m-text);\n}\n\n.mailharbor button:disabled {\n opacity: 0.45;\n cursor: default;\n}\n\n.mailharbor button.primary {\n background: var(--m-primary-bg);\n color: var(--m-primary-fg);\n border-color: transparent;\n}\n\n.mailharbor button.primary:hover:not(:disabled) {\n background: var(--m-primary-bg);\n filter: brightness(0.94);\n color: var(--m-primary-fg);\n}\n\n.mailharbor button.ghost {\n border-color: var(--m-line);\n color: var(--m-text-2);\n}\n\n.mailharbor button.ghost:hover:not(:disabled) {\n border-color: var(--m-line-strong);\n color: var(--m-text);\n}\n\n.mailharbor button.subtle {\n color: var(--m-text-3);\n padding: 0 8px;\n}\n\n.mailharbor button.icon {\n width: 30px;\n padding: 0;\n}\n\n.mailharbor button.danger {\n color: var(--m-danger);\n}\n\n.mailharbor input,\n.mailharbor select,\n.mailharbor textarea {\n font: inherit;\n color: var(--m-text);\n background: var(--m-bg-1);\n border: 1px solid var(--m-line);\n border-radius: var(--m-radius);\n padding: 0 10px;\n height: 32px;\n min-width: 0;\n}\n\n.mailharbor textarea {\n height: auto;\n padding: 8px 10px;\n resize: vertical;\n line-height: 1.6;\n}\n\n.mailharbor select {\n padding-right: 26px;\n appearance: none;\n background-image:\n linear-gradient(45deg, transparent 50%, var(--m-text-3) 50%),\n linear-gradient(135deg, var(--m-text-3) 50%, transparent 50%);\n background-position:\n calc(100% - 15px) 14px,\n calc(100% - 10px) 14px;\n background-size:\n 5px 5px,\n 5px 5px;\n background-repeat: no-repeat;\n}\n\n.mailharbor ::placeholder {\n color: var(--m-caption);\n}\n\n.mailharbor input:focus-visible,\n.mailharbor select:focus-visible,\n.mailharbor textarea:focus-visible,\n.mailharbor button:focus-visible,\n.mailharbor [contenteditable='true']:focus-visible {\n outline: 2px solid rgba(86, 134, 254, 0.55);\n outline-offset: -1px;\n}\n\n.mailharbor .spacer {\n flex: 1;\n}\n\n.mailharbor .muted {\n color: var(--m-text-3);\n}\n\n.mailharbor .small {\n font-size: 12px;\n}\n\n.mailharbor .badge {\n display: inline-flex;\n align-items: center;\n height: 18px;\n padding: 0 6px;\n border-radius: 999px;\n font-size: 10.5px;\n font-weight: 500;\n background: var(--m-bg-3);\n color: var(--m-text-2);\n}\n\n.mailharbor .badge.ok {\n background: rgba(63, 191, 127, 0.16);\n color: var(--m-ok);\n}\n\n.mailharbor .badge.warn {\n background: rgba(221, 134, 41, 0.16);\n color: var(--m-warn);\n}\n\n.mailharbor .error {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 14px;\n color: var(--m-danger);\n background: rgba(242, 90, 90, 0.1);\n border-bottom: 1px solid rgba(242, 90, 90, 0.2);\n font-size: 12.5px;\n}\n\n/* ---------- shell ---------- */\n\n.mailharbor-topbar {\n display: flex;\n align-items: center;\n gap: 10px;\n height: 52px;\n padding: 0 14px;\n border-bottom: 1px solid var(--m-line-soft);\n background: var(--m-bg);\n flex: none;\n}\n\n.mailharbor-account {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n max-width: 220px;\n padding: 4px 8px 4px 4px;\n border-radius: 999px;\n border: 1px solid transparent;\n cursor: default;\n}\n\n.mailharbor-account:hover {\n background: var(--m-hover);\n}\n\n.mailharbor-account .name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-weight: 500;\n}\n\n.mailharbor-account select {\n height: 26px;\n background: transparent;\n border-color: transparent;\n font-weight: 500;\n}\n\n.mailharbor-body {\n display: flex;\n flex: 1;\n min-height: 0;\n position: relative;\n}\n\n.mailharbor button.active {\n background: var(--m-accent-soft);\n color: var(--m-text);\n}\n\n.mailharbor-list {\n display: flex;\n flex-direction: column;\n width: 360px;\n flex: none;\n border-right: 1px solid var(--m-line-soft);\n min-height: 0;\n}\n\n.mailharbor-reader {\n flex: 1;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n\n@media (max-width: 1000px) {\n .mailharbor-list {\n width: 300px;\n }\n}\n\n.mailharbor-narrow .mailharbor-body[data-mode='reader'] .mailharbor-list {\n display: none;\n}\n\n.mailharbor-narrow .mailharbor-body[data-mode='list'] .mailharbor-reader {\n display: none;\n}\n\n/* ---------- segmented control ---------- */\n\n.mailharbor-segment {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n padding: 2px;\n border-radius: 9px;\n background: var(--m-bg-2);\n}\n\n.mailharbor-segment button {\n height: 26px;\n padding: 0 12px;\n border-radius: 7px;\n color: var(--m-text-3);\n font-size: 12px;\n}\n\n.mailharbor-segment button:hover:not(.active) {\n color: var(--m-text-2);\n background: transparent;\n}\n\n.mailharbor-segment button.active {\n background: var(--m-bg-3);\n color: var(--m-text);\n}\n\n/* ---------- list ---------- */\n\n.mailharbor-filters {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 12px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-filters select {\n width: 128px;\n flex: none;\n font-size: 12px;\n}\n\n.mailharbor-filters input[type='search'] {\n flex: 1;\n height: 30px;\n font-size: 12.5px;\n}\n\n.mailharbor-rows {\n flex: 1;\n overflow: auto;\n min-height: 0;\n}\n\n.mailharbor-row {\n display: flex;\n flex-direction: column;\n gap: 3px;\n width: 100%;\n padding: 10px 14px;\n border: 0;\n border-bottom: 1px solid var(--m-line-soft);\n border-left: 2px solid transparent;\n border-radius: 0;\n background: transparent;\n color: inherit;\n text-align: left;\n cursor: pointer;\n user-select: none;\n}\n\n.mailharbor-row:hover {\n background: var(--m-hover);\n}\n\n.mailharbor-row.selected {\n background: var(--m-accent-soft);\n border-left-color: var(--m-accent);\n}\n\n.mailharbor-row .line-1,\n.mailharbor-row .line-2 {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n width: 100%;\n}\n\n.mailharbor-row .from {\n font-weight: 600;\n font-size: 12.5px;\n color: var(--m-text-2);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mailharbor-row .date {\n margin-left: auto;\n color: var(--m-caption);\n font-size: 11px;\n flex: none;\n}\n\n.mailharbor-row .subject {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--m-text-2);\n font-size: 12.5px;\n}\n\n.mailharbor-row .snippet {\n color: var(--m-caption);\n font-size: 11.5px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n width: 100%;\n}\n\n.mailharbor-row.unread .from,\n.mailharbor-row.unread .subject {\n color: var(--m-text);\n}\n\n.mailharbor-row .dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--m-accent);\n flex: none;\n}\n\n.mailharbor-row .paperclip {\n color: var(--m-caption);\n flex: none;\n display: inline-flex;\n}\n\n.mailharbor-list-foot {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-top: 1px solid var(--m-line-soft);\n color: var(--m-text-3);\n font-size: 11.5px;\n flex: none;\n}\n\n.mailharbor-empty {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 10px;\n padding: 48px 24px;\n color: var(--m-text-3);\n text-align: center;\n}\n\n.mailharbor-empty svg {\n opacity: 0.5;\n}\n\n.mailharbor-empty .title {\n color: var(--m-text-2);\n font-size: 13px;\n font-weight: 500;\n}\n\n/* ---------- reader ---------- */\n\n.mailharbor-reader-head {\n padding: 16px 20px 12px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.mailharbor-reader-title {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n}\n\n.mailharbor-reader-title h2 {\n margin: 0;\n font-size: 17px;\n line-height: 1.35;\n font-weight: 600;\n letter-spacing: -0.01em;\n word-break: break-word;\n}\n\n.mailharbor-reader-meta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 4px 10px;\n color: var(--m-text-3);\n font-size: 12px;\n}\n\n.mailharbor-reader-meta .who {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n color: var(--m-text-2);\n}\n\n.mailharbor-reader-meta .who strong {\n color: var(--m-text);\n font-weight: 600;\n}\n\n.mailharbor-reader-actions {\n display: flex;\n align-items: center;\n gap: 6px;\n flex-wrap: wrap;\n}\n\n.mailharbor-reader-body {\n flex: 1;\n min-height: 0;\n display: flex;\n flex-direction: column;\n padding: 14px 18px 18px;\n background: var(--m-bg-2);\n}\n\n.mailharbor-reader-body iframe {\n flex: 1;\n width: 100%;\n border: 0;\n border-radius: 10px;\n background: #fff;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);\n}\n\n.mailharbor-reader-body pre {\n flex: 1;\n overflow: auto;\n margin: 0;\n padding: 20px 22px;\n white-space: pre-wrap;\n word-break: break-word;\n background: #fff;\n border-radius: 10px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);\n color: #171a21;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 12.5px;\n line-height: 1.7;\n}\n\n.mailharbor-attachments {\n padding: 10px 18px;\n border-top: 1px solid var(--m-line-soft);\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n flex: none;\n background: var(--m-bg);\n}\n\n.mailharbor-attachment {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n height: 30px;\n padding: 0 10px;\n border: 1px solid var(--m-line);\n border-radius: var(--m-radius);\n font-size: 12px;\n text-decoration: none;\n color: var(--m-text-2);\n max-width: 260px;\n background: var(--m-bg-1);\n}\n\n.mailharbor-attachment:hover {\n border-color: var(--m-line-strong);\n color: var(--m-text);\n}\n\n.mailharbor-attachment .name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* ---------- AI rail ---------- */\n\n.mailharbor-ai {\n display: flex;\n flex-direction: column;\n width: 340px;\n flex: none;\n min-height: 0;\n border-left: 1px solid var(--m-line-soft);\n background: var(--m-bg);\n}\n\n.mailharbor-ai-head {\n display: flex;\n align-items: center;\n gap: 8px;\n height: 46px;\n padding: 0 8px 0 14px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-ai-head strong {\n font-size: 13px;\n font-weight: 600;\n}\n\n.mailharbor-ai-confirm {\n display: inline-flex;\n gap: 2px;\n}\n\n.mailharbor-ai-context {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 9px 14px 10px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-ai-context strong {\n font-size: 12.5px;\n font-weight: 600;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mailharbor-ai-warn {\n padding: 8px 14px;\n color: var(--m-warn);\n background: rgba(221, 134, 41, 0.1);\n font-size: 12px;\n flex: none;\n}\n\n.mailharbor-ai-quick {\n display: flex;\n gap: 6px;\n padding: 10px 14px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-ai-quick button {\n flex: 1;\n padding: 0 6px;\n}\n\n.mailharbor-ai-list {\n flex: 1;\n min-height: 0;\n overflow: auto;\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 12px 14px;\n}\n\n.mailharbor-ai-empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 6px;\n padding: 30px 12px;\n text-align: center;\n color: var(--m-text-3);\n}\n\n.mailharbor-ai-empty .title {\n color: var(--m-text-2);\n font-weight: 500;\n font-size: 12.5px;\n}\n\n.mailharbor-ai-card {\n display: flex;\n flex-direction: column;\n border: 1px solid var(--m-line-soft);\n border-radius: 10px;\n background: var(--m-bg-1);\n overflow: hidden;\n}\n\n.mailharbor-ai-card.generating {\n opacity: 0.7;\n}\n\n.mailharbor-ai-card-head {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 7px 8px 7px 12px;\n border-bottom: 1px solid var(--m-line-soft);\n font-size: 11.5px;\n color: var(--m-text-3);\n}\n\n.mailharbor-ai-card-head > span:first-child {\n color: var(--m-text-2);\n font-weight: 600;\n}\n\n.mailharbor-ai-summary {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 10px 12px;\n}\n\n.mailharbor-ai-summary section h4 {\n margin: 0 0 3px;\n font-size: 11px;\n color: var(--m-text-3);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n}\n\n.mailharbor-ai-summary section p {\n margin: 0;\n white-space: pre-wrap;\n font-size: 12.5px;\n line-height: 1.65;\n color: var(--m-text);\n}\n\n.mailharbor-ai-text {\n padding: 10px 12px;\n max-height: 320px;\n overflow: auto;\n white-space: pre-wrap;\n font-size: 12.5px;\n line-height: 1.65;\n color: var(--m-text);\n}\n\n.mailharbor-ai-card-actions {\n display: flex;\n align-items: center;\n gap: 4px;\n flex-wrap: wrap;\n padding: 6px 8px;\n border-top: 1px solid var(--m-line-soft);\n}\n\n.mailharbor-ai-card-actions button {\n height: 26px;\n padding: 0 8px;\n font-size: 11.5px;\n}\n\n.mailharbor-ai-ask {\n display: flex;\n gap: 6px;\n padding: 10px 12px;\n border-top: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-ai-ask input {\n flex: 1;\n height: 32px;\n}\n\n.mailharbor-ai-error {\n color: var(--m-danger);\n font-size: 12px;\n padding: 0 2px;\n}\n\n@media (max-width: 1280px) {\n .mailharbor-ai {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n width: min(420px, 94%);\n z-index: 6;\n border-left: 1px solid var(--m-line);\n box-shadow: -12px 0 40px rgba(0, 0, 0, 0.45);\n }\n}\n\n/* ---------- AI summary ---------- */\n\n.mailharbor-divider {\n width: 1px;\n height: 18px;\n margin: 0 4px;\n background: var(--m-line);\n}\n\n.mailharbor-summary {\n display: flex;\n flex-direction: column;\n gap: 16px;\n}\n\n.mailharbor-summary section h4 {\n margin: 0 0 6px;\n font-size: 11.5px;\n font-weight: 600;\n color: var(--m-text-3);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n}\n\n.mailharbor-summary section p {\n margin: 0;\n white-space: pre-wrap;\n line-height: 1.75;\n color: var(--m-text);\n}\n\n/* ---------- modal / forms ---------- */\n\n.mailharbor-portal {\n position: fixed;\n inset: 0;\n z-index: 80;\n height: auto;\n background: transparent;\n pointer-events: none;\n}\n\n.mailharbor-portal .mailharbor-modal-mask {\n pointer-events: auto;\n}\n\n.mailharbor-modal-mask {\n position: fixed;\n inset: 0;\n z-index: 60;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n background: rgba(0, 0, 0, 0.55);\n backdrop-filter: blur(2px);\n}\n\n.mailharbor-modal {\n display: flex;\n flex-direction: column;\n width: min(720px, 100%);\n max-height: 86vh;\n overflow: hidden;\n border: 1px solid var(--m-line);\n border-radius: 14px;\n background: var(--m-bg-1);\n color: var(--m-text);\n box-shadow: var(--m-shadow);\n}\n\n.mailharbor-modal > header {\n display: flex;\n align-items: center;\n gap: 8px;\n height: 52px;\n padding: 0 14px 0 18px;\n border-bottom: 1px solid var(--m-line-soft);\n font-size: 14px;\n font-weight: 600;\n}\n\n.mailharbor-modal .content {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 18px;\n overflow: auto;\n}\n\n.mailharbor-modal > footer {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 18px;\n border-top: 1px solid var(--m-line-soft);\n}\n\n.mailharbor-field {\n display: flex;\n flex-direction: column;\n gap: 5px;\n min-width: 0;\n}\n\n.mailharbor-field > input,\n.mailharbor-field > select,\n.mailharbor-field > textarea,\n.mailharbor-card > textarea {\n width: 100%;\n min-width: 0;\n}\n\n.mailharbor-field > label {\n font-size: 11.5px;\n color: var(--m-text-3);\n}\n\n.mailharbor-grid {\n display: grid;\n grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);\n gap: 10px;\n min-width: 0;\n}\n\n.mailharbor-grid.three {\n grid-template-columns: minmax(0, 2fr) minmax(72px, 90px) auto;\n align-items: end;\n min-width: 0;\n}\n\n.mailharbor-toggle {\n display: inline-flex;\n align-items: center;\n gap: 7px;\n color: var(--m-text-2);\n font-size: 12.5px;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.mailharbor-toggle input[type='checkbox'] {\n width: 14px;\n height: 14px;\n accent-color: var(--m-accent);\n}\n\n/* ---------- composer ---------- */\n\n.mailharbor-toolbar {\n display: flex;\n align-items: center;\n gap: 2px;\n flex-wrap: wrap;\n padding: 6px;\n border: 1px solid var(--m-line-soft);\n border-bottom: 0;\n border-radius: 10px 10px 0 0;\n background: var(--m-bg-2);\n}\n\n.mailharbor-toolbar button {\n height: 26px;\n padding: 0 9px;\n border-radius: 6px;\n color: var(--m-text-2);\n font-size: 12px;\n}\n\n.mailharbor-toolbar button:hover {\n background: var(--m-bg-3);\n color: var(--m-text);\n}\n\n.mailharbor-editor {\n min-height: 240px;\n max-height: 44vh;\n overflow: auto;\n padding: 14px 16px;\n border: 1px solid var(--m-line-soft);\n border-radius: 0 0 10px 10px;\n background: #fff;\n color: #171a21;\n outline: none;\n font-size: 13.5px;\n line-height: 1.7;\n}\n\n.mailharbor-editor:empty::before {\n content: attr(data-placeholder);\n color: #9aa0ab;\n}\n\n/* ---------- inline confirm ---------- */\n\n.mailharbor-confirm {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-radius: 8px;\n border: 1px solid var(--m-line);\n background: var(--m-bg-2);\n color: var(--m-text-2);\n font-size: 12.5px;\n}\n\n/* ---------- selection / extraction ---------- */\n\n.mailharbor-selectbar {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n margin: 0 12px 8px;\n border: 1px solid var(--m-accent-soft);\n border-radius: 9px;\n background: var(--m-accent-soft);\n}\n\n.mailharbor-selectbar select {\n height: 28px;\n max-width: 200px;\n}\n\n.mailharbor-row.selectable input[type='checkbox'] {\n width: 15px;\n height: 15px;\n flex: none;\n accent-color: var(--m-accent);\n}\n\n.mailharbor-extract-row {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n padding: 9px 10px;\n border: 1px solid var(--m-line-soft);\n border-radius: 9px;\n background: var(--m-bg-1);\n cursor: pointer;\n}\n\n.mailharbor-extract-row input {\n margin-top: 2px;\n accent-color: var(--m-accent);\n}\n\n.mailharbor-extract-row > span {\n display: flex;\n flex-direction: column;\n gap: 3px;\n min-width: 0;\n}\n\n.mailharbor-kv {\n display: grid;\n grid-template-columns: auto minmax(0, 1fr);\n gap: 6px 12px;\n margin: 0;\n font-size: 12.5px;\n}\n\n.mailharbor-kv dt {\n color: var(--m-text-3);\n}\n\n.mailharbor-kv dd {\n margin: 0;\n overflow-wrap: anywhere;\n}\n\n/* ---------- tasks panel ---------- */\n\n.mailharbor .chip {\n height: 26px;\n padding: 0 10px;\n border: 1px solid var(--m-line);\n border-radius: 999px;\n color: var(--m-text-3);\n font-size: 12px;\n}\n\n.mailharbor .chip.active {\n background: var(--m-accent-soft);\n border-color: transparent;\n color: var(--m-text);\n}\n\n.tasks-body {\n flex: 1;\n min-height: 0;\n overflow: auto;\n display: flex;\n flex-direction: column;\n gap: 18px;\n padding: 18px 20px 48px;\n width: 100%;\n max-width: 940px;\n margin: 0 auto;\n}\n\n.tasks-overview {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));\n gap: 12px;\n}\n\n.tasks-progress,\n.tasks-project {\n display: flex;\n flex-direction: column;\n gap: 7px;\n padding: 12px 14px;\n border: 1px solid var(--m-line-soft);\n border-radius: 12px;\n background: var(--m-bg-1);\n min-width: 0;\n}\n\n.tasks-progress-head,\n.tasks-project-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n min-width: 0;\n}\n\n.tasks-project-head .name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 12.5px;\n font-weight: 500;\n}\n\n.tasks-bar {\n height: 6px;\n border-radius: 999px;\n background: var(--m-bg-3);\n overflow: hidden;\n}\n\n.tasks-bar span {\n display: block;\n height: 100%;\n border-radius: 999px;\n background: var(--m-accent);\n transition: width 200ms ease;\n}\n\n.tasks-bar.small {\n height: 4px;\n}\n\n.tasks-filters {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n.tasks-list {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.tasks-row {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 9px 12px;\n border: 1px solid var(--m-line-soft);\n border-radius: 10px;\n background: var(--m-bg-1);\n}\n\n.tasks-row input[type='checkbox'] {\n width: 16px;\n height: 16px;\n flex: none;\n accent-color: var(--m-accent);\n}\n\n.tasks-row .tasks-title {\n flex: 1;\n min-width: 0;\n height: auto;\n padding: 0;\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 4px;\n text-align: left;\n border: 0;\n background: transparent;\n}\n\n.tasks-row .tasks-title:hover {\n background: transparent;\n}\n\n.tasks-row .tasks-title > span:first-child {\n font-size: 13px;\n color: var(--m-text);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n max-width: 100%;\n}\n\n.tasks-row.done .tasks-title > span:first-child {\n color: var(--m-text-3);\n text-decoration: line-through;\n}\n\n.tasks-meta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n font-size: 11px;\n color: var(--m-text-3);\n}\n\n.tasks-meta .due.overdue {\n color: var(--m-danger);\n}\n\n.tasks-list-wrap {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n/* ---------- markdown ---------- */\n\n.mailharbor-markdown {\n display: flex;\n flex-direction: column;\n gap: 8px;\n font-size: 12.5px;\n line-height: 1.68;\n overflow-wrap: anywhere;\n}\n\n.mailharbor-markdown > *:first-child {\n margin-top: 0;\n}\n\n.mailharbor-markdown > *:last-child {\n margin-bottom: 0;\n}\n\n.mailharbor-markdown p {\n margin: 0;\n white-space: pre-wrap;\n}\n\n.mailharbor-markdown h1,\n.mailharbor-markdown h2,\n.mailharbor-markdown h3,\n.mailharbor-markdown h4,\n.mailharbor-markdown h5,\n.mailharbor-markdown h6 {\n margin: 4px 0 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--m-text);\n}\n\n.mailharbor-markdown ul,\n.mailharbor-markdown ol {\n margin: 0;\n padding-left: 20px;\n display: flex;\n flex-direction: column;\n gap: 3px;\n}\n\n.mailharbor-markdown li::marker {\n color: var(--m-text-3);\n}\n\n.mailharbor-markdown a {\n color: var(--m-accent);\n text-decoration: none;\n}\n\n.mailharbor-markdown a:hover {\n text-decoration: underline;\n}\n\n.mailharbor-markdown code {\n padding: 1px 4px;\n border-radius: 4px;\n background: var(--m-bg-2);\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 11.5px;\n}\n\n.mailharbor-markdown pre {\n margin: 0;\n padding: 10px 12px;\n border-radius: 8px;\n background: var(--m-bg);\n border: 1px solid var(--m-line-soft);\n overflow: auto;\n}\n\n.mailharbor-markdown pre code {\n padding: 0;\n background: transparent;\n}\n\n.mailharbor-markdown blockquote {\n margin: 0;\n padding-left: 10px;\n border-left: 2px solid var(--m-line);\n color: var(--m-text-2);\n}\n\n.mailharbor-markdown hr {\n width: 100%;\n margin: 4px 0;\n border: 0;\n border-top: 1px solid var(--m-line-soft);\n}\n\n/* ---------- composer AI ---------- */\n\n.mailharbor-toolbar button.ai {\n color: var(--m-accent);\n}\n\n.mailharbor-compose-foot {\n display: flex;\n align-items: center;\n gap: 10px;\n width: 100%;\n flex-wrap: wrap;\n}\n\n.mailharbor-versions {\n display: flex;\n align-items: center;\n gap: 6px;\n flex-wrap: wrap;\n padding-top: 8px;\n}\n\n.mailharbor-versions .chip {\n height: 22px;\n padding: 0 8px;\n border: 1px solid var(--m-line);\n border-radius: 999px;\n color: var(--m-text-3);\n font-size: 11px;\n}\n\n.mailharbor-versions .chip.active {\n background: var(--m-accent-soft);\n border-color: transparent;\n color: var(--m-text);\n}\n\n/* ---------- settings ---------- */\n\n.mailharbor.mailharbor-settings-page {\n height: auto;\n min-height: 100%;\n overflow: visible;\n}\n\n.mailharbor-settings {\n flex: 0 0 auto;\n width: 100%;\n max-width: 100%;\n min-width: 0;\n padding: 18px 20px 48px;\n display: flex;\n flex-direction: column;\n gap: 16px;\n overflow-x: hidden;\n}\n\n.mailharbor-settings > * {\n min-width: 0;\n}\n\n.mailharbor-settings h2 {\n margin: 0;\n font-size: 18px;\n font-weight: 600;\n letter-spacing: -0.01em;\n}\n\n.mailharbor-settings h3 {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--m-text-3);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n}\n\n.mailharbor-card {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 16px;\n border: 1px solid var(--m-line-soft);\n border-radius: 12px;\n background: var(--m-bg-1);\n min-width: 0;\n overflow: hidden;\n}\n\n.mailharbor-account-row {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n.mailharbor-account-row .meta {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n\n.mailharbor-account-row .meta strong {\n font-weight: 600;\n}\n\n.mailharbor-account-row .meta span {\n color: var(--m-text-3);\n font-size: 11.5px;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.mailharbor .avatar {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n border-radius: 50%;\n background: var(--m-accent-soft);\n color: var(--m-accent);\n font-weight: 600;\n}\n\n.mailharbor-log {\n padding: 10px 12px;\n border-radius: 8px;\n background: var(--m-bg);\n border: 1px solid var(--m-line-soft);\n color: var(--m-text-2);\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 11px;\n line-height: 1.6;\n max-height: 220px;\n overflow: auto;\n white-space: pre-wrap;\n}\n\n.mailharbor-kv {\n display: grid;\n grid-template-columns: minmax(120px, 170px) 1fr;\n gap: 6px 14px;\n font-size: 12px;\n margin: 0;\n}\n\n.mailharbor-kv dt {\n color: var(--m-text-3);\n}\n\n.mailharbor-kv dd {\n margin: 0;\n color: var(--m-text-2);\n word-break: break-all;\n}\n\n.mailharbor hr {\n border: 0;\n border-top: 1px solid var(--m-line-soft);\n margin: 2px 0;\n}\n";
38
+ var styles_default = "/* MailHarbor panel + settings.\n Tokens come from the DSH shell (dark by default); fallbacks match the\n default dark theme so the panel also renders in a bare host page.\n Component rules are all scoped under `.mailharbor` and the generic element\n reset lives in `:where()` so it cannot outrank a component class. */\n\n.mailharbor {\n --m-bg: var(--dsw-alias-bg-base, #151517);\n --m-bg-1: var(--dsw-alias-bg-layer-1, #232324);\n --m-bg-2: var(--dsw-alias-bg-layer-2, #2c2c2e);\n --m-bg-3: var(--dsw-alias-bg-layer-3, #353638);\n --m-line: var(--dsw-alias-border-l2, #ffffff1f);\n --m-line-soft: var(--dsw-alias-border-l1, #ffffff0f);\n --m-line-strong: var(--dsw-alias-border-l3, #ffffff29);\n --m-text: var(--dsw-alias-label-primary, #f9fafb);\n --m-text-2: var(--dsw-alias-label-secondary, #cfd3d6);\n --m-text-3: var(--dsw-alias-label-tertiary, #adb2b8);\n --m-caption: var(--dsw-alias-label-caption, #81858c);\n --m-accent: #5686fe;\n --m-accent-soft: rgba(86, 134, 254, 0.14);\n --m-primary-bg: var(--dsw-alias-brand-primary, #f9fafb);\n --m-primary-fg: var(--dsw-alias-label-primary-foreground, #0f1115);\n --m-hover: var(--dsw-alias-interactive-bg-hover, #ffffff14);\n --m-danger: #f25a5a;\n --m-ok: #3fbf7f;\n --m-warn: var(--dsw-alias-state-warn-label, #dd8629);\n --m-radius: 8px;\n --m-shadow: 0 18px 60px rgba(0, 0, 0, 0.5);\n\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n background: var(--m-bg);\n color: var(--m-text);\n font-size: 13px;\n line-height: 1.5;\n -webkit-font-smoothing: antialiased;\n}\n\n.mailharbor *,\n.mailharbor *::before,\n.mailharbor *::after {\n box-sizing: border-box;\n}\n\n/* ---------- base controls ---------- */\n\n.mailharbor :where(button) {\n font: inherit;\n color: inherit;\n background: transparent;\n border: 0;\n margin: 0;\n cursor: pointer;\n}\n\n.mailharbor button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n height: 30px;\n padding: 0 12px;\n border-radius: var(--m-radius);\n border: 1px solid transparent;\n color: var(--m-text-2);\n font-size: 12.5px;\n font-weight: 500;\n white-space: nowrap;\n transition:\n background 120ms ease,\n color 120ms ease,\n border-color 120ms ease;\n}\n\n.mailharbor button:hover:not(:disabled) {\n background: var(--m-hover);\n color: var(--m-text);\n}\n\n.mailharbor button:disabled {\n opacity: 0.45;\n cursor: default;\n}\n\n.mailharbor button.primary {\n background: var(--m-primary-bg);\n color: var(--m-primary-fg);\n border-color: transparent;\n}\n\n.mailharbor button.primary:hover:not(:disabled) {\n background: var(--m-primary-bg);\n filter: brightness(0.94);\n color: var(--m-primary-fg);\n}\n\n.mailharbor button.ghost {\n border-color: var(--m-line);\n color: var(--m-text-2);\n}\n\n.mailharbor button.ghost:hover:not(:disabled) {\n border-color: var(--m-line-strong);\n color: var(--m-text);\n}\n\n.mailharbor button.subtle {\n color: var(--m-text-3);\n padding: 0 8px;\n}\n\n.mailharbor button.icon {\n width: 30px;\n padding: 0;\n}\n\n.mailharbor button.danger {\n color: var(--m-danger);\n}\n\n.mailharbor input,\n.mailharbor select,\n.mailharbor textarea {\n font: inherit;\n color: var(--m-text);\n background: var(--m-bg-1);\n border: 1px solid var(--m-line);\n border-radius: var(--m-radius);\n padding: 0 10px;\n height: 32px;\n min-width: 0;\n}\n\n.mailharbor textarea {\n height: auto;\n padding: 8px 10px;\n resize: vertical;\n line-height: 1.6;\n}\n\n.mailharbor select {\n padding-right: 26px;\n appearance: none;\n background-image:\n linear-gradient(45deg, transparent 50%, var(--m-text-3) 50%),\n linear-gradient(135deg, var(--m-text-3) 50%, transparent 50%);\n background-position:\n calc(100% - 15px) 14px,\n calc(100% - 10px) 14px;\n background-size:\n 5px 5px,\n 5px 5px;\n background-repeat: no-repeat;\n}\n\n.mailharbor ::placeholder {\n color: var(--m-caption);\n}\n\n.mailharbor input:focus-visible,\n.mailharbor select:focus-visible,\n.mailharbor textarea:focus-visible,\n.mailharbor button:focus-visible,\n.mailharbor [contenteditable='true']:focus-visible {\n outline: 2px solid rgba(86, 134, 254, 0.55);\n outline-offset: -1px;\n}\n\n.mailharbor .spacer {\n flex: 1;\n}\n\n.mailharbor .muted {\n color: var(--m-text-3);\n}\n\n.mailharbor .small {\n font-size: 12px;\n}\n\n.mailharbor .badge {\n display: inline-flex;\n align-items: center;\n height: 18px;\n padding: 0 6px;\n border-radius: 999px;\n font-size: 10.5px;\n font-weight: 500;\n background: var(--m-bg-3);\n color: var(--m-text-2);\n}\n\n.mailharbor .badge.ok {\n background: rgba(63, 191, 127, 0.16);\n color: var(--m-ok);\n}\n\n.mailharbor .badge.warn {\n background: rgba(221, 134, 41, 0.16);\n color: var(--m-warn);\n}\n\n.mailharbor .error {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 14px;\n color: var(--m-danger);\n background: rgba(242, 90, 90, 0.1);\n border-bottom: 1px solid rgba(242, 90, 90, 0.2);\n font-size: 12.5px;\n}\n\n/* ---------- shell ---------- */\n\n.mailharbor-topbar {\n display: flex;\n align-items: center;\n gap: 10px;\n height: 52px;\n padding: 0 14px;\n border-bottom: 1px solid var(--m-line-soft);\n background: var(--m-bg);\n flex: none;\n}\n\n.mailharbor-account {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n max-width: 220px;\n padding: 4px 8px 4px 4px;\n border-radius: 999px;\n border: 1px solid transparent;\n cursor: default;\n}\n\n.mailharbor-account:hover {\n background: var(--m-hover);\n}\n\n.mailharbor-account .name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-weight: 500;\n}\n\n.mailharbor-account select {\n height: 26px;\n background: transparent;\n border-color: transparent;\n font-weight: 500;\n}\n\n.mailharbor-body {\n display: flex;\n flex: 1;\n min-height: 0;\n position: relative;\n}\n\n.mailharbor button.active {\n background: var(--m-accent-soft);\n color: var(--m-text);\n}\n\n.mailharbor-list {\n display: flex;\n flex-direction: column;\n width: 360px;\n flex: none;\n border-right: 1px solid var(--m-line-soft);\n min-height: 0;\n}\n\n.mailharbor-reader {\n flex: 1;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n\n@media (max-width: 1000px) {\n .mailharbor-list {\n width: 300px;\n }\n}\n\n.mailharbor-narrow .mailharbor-body[data-mode='reader'] .mailharbor-list {\n display: none;\n}\n\n.mailharbor-narrow .mailharbor-body[data-mode='list'] .mailharbor-reader {\n display: none;\n}\n\n/* ---------- segmented control ---------- */\n\n.mailharbor-segment {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n padding: 2px;\n border-radius: 9px;\n background: var(--m-bg-2);\n}\n\n.mailharbor-segment button {\n height: 26px;\n padding: 0 12px;\n border-radius: 7px;\n color: var(--m-text-3);\n font-size: 12px;\n}\n\n.mailharbor-segment button:hover:not(.active) {\n color: var(--m-text-2);\n background: transparent;\n}\n\n.mailharbor-segment button.active {\n background: var(--m-bg-3);\n color: var(--m-text);\n}\n\n/* ---------- list ---------- */\n\n.mailharbor-filters {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 12px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-filters select {\n width: 128px;\n flex: none;\n font-size: 12px;\n}\n\n.mailharbor-filters input[type='search'] {\n flex: 1;\n height: 30px;\n font-size: 12.5px;\n}\n\n.mailharbor-rows {\n flex: 1;\n overflow: auto;\n min-height: 0;\n}\n\n.mailharbor-row {\n display: flex;\n flex-direction: column;\n gap: 3px;\n width: 100%;\n padding: 10px 14px;\n border: 0;\n border-bottom: 1px solid var(--m-line-soft);\n border-left: 2px solid transparent;\n border-radius: 0;\n background: transparent;\n color: inherit;\n text-align: left;\n cursor: pointer;\n user-select: none;\n}\n\n.mailharbor-row:hover {\n background: var(--m-hover);\n}\n\n.mailharbor-row.selected {\n background: var(--m-accent-soft);\n border-left-color: var(--m-accent);\n}\n\n.mailharbor-row .line-1,\n.mailharbor-row .line-2 {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n width: 100%;\n}\n\n.mailharbor-row .from {\n font-weight: 600;\n font-size: 12.5px;\n color: var(--m-text-2);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mailharbor-row .date {\n margin-left: auto;\n color: var(--m-caption);\n font-size: 11px;\n flex: none;\n}\n\n.mailharbor-row .subject {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--m-text-2);\n font-size: 12.5px;\n}\n\n.mailharbor-row .snippet {\n color: var(--m-caption);\n font-size: 11.5px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n width: 100%;\n}\n\n.mailharbor-row.unread .from,\n.mailharbor-row.unread .subject {\n color: var(--m-text);\n}\n\n.mailharbor-row .dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--m-accent);\n flex: none;\n}\n\n.mailharbor-row .paperclip {\n color: var(--m-caption);\n flex: none;\n display: inline-flex;\n}\n\n.mailharbor-list-foot {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-top: 1px solid var(--m-line-soft);\n color: var(--m-text-3);\n font-size: 11.5px;\n flex: none;\n}\n\n.mailharbor-empty {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 10px;\n padding: 48px 24px;\n color: var(--m-text-3);\n text-align: center;\n}\n\n.mailharbor-empty svg {\n opacity: 0.5;\n}\n\n.mailharbor-empty .title {\n color: var(--m-text-2);\n font-size: 13px;\n font-weight: 500;\n}\n\n/* ---------- reader ---------- */\n\n.mailharbor-reader-head {\n padding: 16px 20px 12px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.mailharbor-reader-title {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n}\n\n.mailharbor-reader-title h2 {\n margin: 0;\n font-size: 17px;\n line-height: 1.35;\n font-weight: 600;\n letter-spacing: -0.01em;\n word-break: break-word;\n}\n\n.mailharbor-reader-meta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 4px 10px;\n color: var(--m-text-3);\n font-size: 12px;\n}\n\n.mailharbor-reader-meta .who {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n color: var(--m-text-2);\n}\n\n.mailharbor-reader-meta .who strong {\n color: var(--m-text);\n font-weight: 600;\n}\n\n.mailharbor-reader-actions {\n display: flex;\n align-items: center;\n gap: 6px;\n flex-wrap: wrap;\n}\n\n.mailharbor-reader-body {\n flex: 1;\n min-height: 0;\n display: flex;\n flex-direction: column;\n padding: 14px 18px 18px;\n background: var(--m-bg-2);\n}\n\n.mailharbor-reader-body iframe {\n flex: 1;\n width: 100%;\n border: 0;\n border-radius: 10px;\n background: #fff;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);\n}\n\n.mailharbor-reader-body pre {\n flex: 1;\n overflow: auto;\n margin: 0;\n padding: 20px 22px;\n white-space: pre-wrap;\n word-break: break-word;\n background: #fff;\n border-radius: 10px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);\n color: #171a21;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 12.5px;\n line-height: 1.7;\n}\n\n.mailharbor-attachments {\n padding: 10px 18px;\n border-top: 1px solid var(--m-line-soft);\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n flex: none;\n background: var(--m-bg);\n}\n\n.mailharbor-attachment {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n height: 30px;\n padding: 0 10px;\n border: 1px solid var(--m-line);\n border-radius: var(--m-radius);\n font-size: 12px;\n text-decoration: none;\n color: var(--m-text-2);\n max-width: 260px;\n background: var(--m-bg-1);\n}\n\n.mailharbor-attachment:hover {\n border-color: var(--m-line-strong);\n color: var(--m-text);\n}\n\n.mailharbor-attachment .name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* ---------- AI rail ---------- */\n\n.mailharbor-ai {\n display: flex;\n flex-direction: column;\n width: 340px;\n flex: none;\n min-height: 0;\n border-left: 1px solid var(--m-line-soft);\n background: var(--m-bg);\n}\n\n.mailharbor-ai-head {\n display: flex;\n align-items: center;\n gap: 8px;\n height: 46px;\n padding: 0 8px 0 14px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-ai-head strong {\n font-size: 13px;\n font-weight: 600;\n}\n\n.mailharbor-ai-confirm {\n display: inline-flex;\n gap: 2px;\n}\n\n.mailharbor-ai-context {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 9px 14px 10px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-ai-context strong {\n font-size: 12.5px;\n font-weight: 600;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mailharbor-ai-warn {\n padding: 8px 14px;\n color: var(--m-warn);\n background: rgba(221, 134, 41, 0.1);\n font-size: 12px;\n flex: none;\n}\n\n.mailharbor-ai-quick {\n display: flex;\n gap: 6px;\n padding: 10px 14px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-ai-quick button {\n flex: 1;\n padding: 0 6px;\n}\n\n.mailharbor-ai-list {\n flex: 1;\n min-height: 0;\n overflow: auto;\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 12px 14px;\n}\n\n.mailharbor-ai-empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 6px;\n padding: 30px 12px;\n text-align: center;\n color: var(--m-text-3);\n}\n\n.mailharbor-ai-empty .title {\n color: var(--m-text-2);\n font-weight: 500;\n font-size: 12.5px;\n}\n\n.mailharbor-ai-card {\n display: flex;\n flex-direction: column;\n border: 1px solid var(--m-line-soft);\n border-radius: 10px;\n background: var(--m-bg-1);\n overflow: hidden;\n}\n\n.mailharbor-ai-card.generating {\n opacity: 0.7;\n}\n\n.mailharbor-ai-card-head {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 7px 8px 7px 12px;\n border-bottom: 1px solid var(--m-line-soft);\n font-size: 11.5px;\n color: var(--m-text-3);\n}\n\n.mailharbor-ai-card-head > span:first-child {\n color: var(--m-text-2);\n font-weight: 600;\n}\n\n.mailharbor-ai-summary {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 10px 12px;\n}\n\n.mailharbor-ai-summary section h4 {\n margin: 0 0 3px;\n font-size: 11px;\n color: var(--m-text-3);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n}\n\n.mailharbor-ai-summary section p {\n margin: 0;\n white-space: pre-wrap;\n font-size: 12.5px;\n line-height: 1.65;\n color: var(--m-text);\n}\n\n.mailharbor-ai-text {\n padding: 10px 12px;\n max-height: 320px;\n overflow: auto;\n white-space: pre-wrap;\n font-size: 12.5px;\n line-height: 1.65;\n color: var(--m-text);\n}\n\n.mailharbor-ai-card-actions {\n display: flex;\n align-items: center;\n gap: 4px;\n flex-wrap: wrap;\n padding: 6px 8px;\n border-top: 1px solid var(--m-line-soft);\n}\n\n.mailharbor-ai-card-actions button {\n height: 26px;\n padding: 0 8px;\n font-size: 11.5px;\n}\n\n.mailharbor-ai-ask {\n display: flex;\n gap: 6px;\n padding: 10px 12px;\n border-top: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-ai-ask input {\n flex: 1;\n height: 32px;\n}\n\n.mailharbor-ai-error {\n color: var(--m-danger);\n font-size: 12px;\n padding: 0 2px;\n}\n\n@media (max-width: 1280px) {\n .mailharbor-ai {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n width: min(420px, 94%);\n z-index: 6;\n border-left: 1px solid var(--m-line);\n box-shadow: -12px 0 40px rgba(0, 0, 0, 0.45);\n }\n}\n\n/* ---------- AI summary ---------- */\n\n.mailharbor-divider {\n width: 1px;\n height: 18px;\n margin: 0 4px;\n background: var(--m-line);\n}\n\n.mailharbor-summary {\n display: flex;\n flex-direction: column;\n gap: 16px;\n}\n\n.mailharbor-summary section h4 {\n margin: 0 0 6px;\n font-size: 11.5px;\n font-weight: 600;\n color: var(--m-text-3);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n}\n\n.mailharbor-summary section p {\n margin: 0;\n white-space: pre-wrap;\n line-height: 1.75;\n color: var(--m-text);\n}\n\n/* ---------- modal / forms ---------- */\n\n.mailharbor-portal {\n position: fixed;\n inset: 0;\n z-index: 80;\n height: auto;\n background: transparent;\n pointer-events: none;\n}\n\n.mailharbor-portal .mailharbor-modal-mask {\n pointer-events: auto;\n}\n\n.mailharbor-modal-mask {\n position: fixed;\n inset: 0;\n z-index: 60;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n background: rgba(0, 0, 0, 0.55);\n backdrop-filter: blur(2px);\n}\n\n.mailharbor-modal {\n display: flex;\n flex-direction: column;\n width: min(720px, 100%);\n max-height: 86vh;\n overflow: hidden;\n border: 1px solid var(--m-line);\n border-radius: 14px;\n background: var(--m-bg-1);\n color: var(--m-text);\n box-shadow: var(--m-shadow);\n}\n\n.mailharbor-modal > header {\n display: flex;\n align-items: center;\n gap: 8px;\n height: 52px;\n padding: 0 14px 0 18px;\n border-bottom: 1px solid var(--m-line-soft);\n font-size: 14px;\n font-weight: 600;\n}\n\n.mailharbor-modal .content {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 18px;\n overflow: auto;\n}\n\n.mailharbor-modal > footer {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 18px;\n border-top: 1px solid var(--m-line-soft);\n}\n\n.mailharbor-field {\n display: flex;\n flex-direction: column;\n gap: 5px;\n min-width: 0;\n}\n\n.mailharbor-field > input,\n.mailharbor-field > select,\n.mailharbor-field > textarea,\n.mailharbor-card > textarea {\n width: 100%;\n min-width: 0;\n}\n\n.mailharbor-field > label {\n font-size: 11.5px;\n color: var(--m-text-3);\n}\n\n.mailharbor-grid {\n display: grid;\n grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);\n gap: 10px;\n min-width: 0;\n}\n\n.mailharbor-grid.three {\n grid-template-columns: minmax(0, 2fr) minmax(72px, 90px) auto;\n align-items: end;\n min-width: 0;\n}\n\n.mailharbor-toggle {\n display: inline-flex;\n align-items: center;\n gap: 7px;\n color: var(--m-text-2);\n font-size: 12.5px;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.mailharbor-toggle input[type='checkbox'] {\n width: 14px;\n height: 14px;\n accent-color: var(--m-accent);\n}\n\n/* ---------- composer ---------- */\n\n.mailharbor-toolbar {\n display: flex;\n align-items: center;\n gap: 2px;\n flex-wrap: wrap;\n padding: 6px;\n border: 1px solid var(--m-line-soft);\n border-bottom: 0;\n border-radius: 10px 10px 0 0;\n background: var(--m-bg-2);\n}\n\n.mailharbor-toolbar button {\n height: 26px;\n padding: 0 9px;\n border-radius: 6px;\n color: var(--m-text-2);\n font-size: 12px;\n}\n\n.mailharbor-toolbar button:hover {\n background: var(--m-bg-3);\n color: var(--m-text);\n}\n\n.mailharbor-editor {\n min-height: 240px;\n max-height: 44vh;\n overflow: auto;\n padding: 14px 16px;\n border: 1px solid var(--m-line-soft);\n border-radius: 0 0 10px 10px;\n background: #fff;\n color: #171a21;\n outline: none;\n font-size: 13.5px;\n line-height: 1.7;\n}\n\n.mailharbor-editor:empty::before {\n content: attr(data-placeholder);\n color: #9aa0ab;\n}\n\n/* ---------- inline confirm ---------- */\n\n.mailharbor-confirm {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-radius: 8px;\n border: 1px solid var(--m-line);\n background: var(--m-bg-2);\n color: var(--m-text-2);\n font-size: 12.5px;\n}\n\n/* ---------- selection / extraction ---------- */\n\n.mailharbor-selectbar {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n margin: 0 12px 8px;\n border: 1px solid var(--m-accent-soft);\n border-radius: 9px;\n background: var(--m-accent-soft);\n}\n\n.mailharbor-selectbar select {\n height: 28px;\n max-width: 200px;\n}\n\n.mailharbor-row.selectable input[type='checkbox'] {\n width: 15px;\n height: 15px;\n flex: none;\n accent-color: var(--m-accent);\n}\n\n.mailharbor-extract-row {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n padding: 9px 10px;\n border: 1px solid var(--m-line-soft);\n border-radius: 9px;\n background: var(--m-bg-1);\n cursor: pointer;\n}\n\n.mailharbor-extract-row input {\n margin-top: 2px;\n accent-color: var(--m-accent);\n}\n\n.mailharbor-extract-row > span {\n display: flex;\n flex-direction: column;\n gap: 3px;\n min-width: 0;\n}\n\n.mailharbor-kv {\n display: grid;\n grid-template-columns: auto minmax(0, 1fr);\n gap: 6px 12px;\n margin: 0;\n font-size: 12.5px;\n}\n\n.mailharbor-kv dt {\n color: var(--m-text-3);\n}\n\n.mailharbor-kv dd {\n margin: 0;\n overflow-wrap: anywhere;\n}\n\n/* ---------- tasks panel ---------- */\n\n.mailharbor .chip {\n height: 26px;\n padding: 0 10px;\n border: 1px solid var(--m-line);\n border-radius: 999px;\n color: var(--m-text-3);\n font-size: 12px;\n}\n\n.mailharbor .chip.active {\n background: var(--m-accent-soft);\n border-color: transparent;\n color: var(--m-text);\n}\n\n.tasks-body {\n flex: 1;\n min-height: 0;\n overflow: auto;\n display: flex;\n flex-direction: column;\n gap: 18px;\n padding: 18px 20px 48px;\n width: 100%;\n max-width: 940px;\n margin: 0 auto;\n}\n\n.tasks-overview {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));\n gap: 12px;\n}\n\n.tasks-progress,\n.tasks-project {\n display: flex;\n flex-direction: column;\n gap: 7px;\n padding: 12px 14px;\n border: 1px solid var(--m-line-soft);\n border-radius: 12px;\n background: var(--m-bg-1);\n min-width: 0;\n}\n\n.tasks-progress-head,\n.tasks-project-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n min-width: 0;\n}\n\n.tasks-project-head .name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 12.5px;\n font-weight: 500;\n}\n\n.tasks-bar {\n height: 6px;\n border-radius: 999px;\n background: var(--m-bg-3);\n overflow: hidden;\n}\n\n.tasks-bar span {\n display: block;\n height: 100%;\n border-radius: 999px;\n background: var(--m-accent);\n transition: width 200ms ease;\n}\n\n.tasks-bar.small {\n height: 4px;\n}\n\n.tasks-filters {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n.tasks-list {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.tasks-row {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 9px 12px;\n border: 1px solid var(--m-line-soft);\n border-radius: 10px;\n background: var(--m-bg-1);\n}\n\n.tasks-row input[type='checkbox'] {\n width: 16px;\n height: 16px;\n flex: none;\n accent-color: var(--m-accent);\n}\n\n.tasks-row .tasks-title {\n flex: 1;\n min-width: 0;\n height: auto;\n padding: 0;\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 4px;\n text-align: left;\n border: 0;\n background: transparent;\n}\n\n.tasks-row .tasks-title:hover {\n background: transparent;\n}\n\n.tasks-row .tasks-title > span:first-child {\n font-size: 13px;\n color: var(--m-text);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n max-width: 100%;\n}\n\n.tasks-row.done .tasks-title > span:first-child {\n color: var(--m-text-3);\n text-decoration: line-through;\n}\n\n.tasks-meta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n font-size: 11px;\n color: var(--m-text-3);\n}\n\n.tasks-meta .due.overdue {\n color: var(--m-danger);\n}\n\n.tasks-list-wrap {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n/* ---------- markdown ---------- */\n\n.mailharbor-markdown {\n display: flex;\n flex-direction: column;\n gap: 8px;\n font-size: 12.5px;\n line-height: 1.68;\n overflow-wrap: anywhere;\n}\n\n.mailharbor-markdown > *:first-child {\n margin-top: 0;\n}\n\n.mailharbor-markdown > *:last-child {\n margin-bottom: 0;\n}\n\n.mailharbor-markdown p {\n margin: 0;\n white-space: pre-wrap;\n}\n\n.mailharbor-markdown h1,\n.mailharbor-markdown h2,\n.mailharbor-markdown h3,\n.mailharbor-markdown h4,\n.mailharbor-markdown h5,\n.mailharbor-markdown h6 {\n margin: 4px 0 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--m-text);\n}\n\n.mailharbor-markdown ul,\n.mailharbor-markdown ol {\n margin: 0;\n padding-left: 20px;\n display: flex;\n flex-direction: column;\n gap: 3px;\n}\n\n.mailharbor-markdown li::marker {\n color: var(--m-text-3);\n}\n\n.mailharbor-markdown a {\n color: var(--m-accent);\n text-decoration: none;\n}\n\n.mailharbor-markdown a:hover {\n text-decoration: underline;\n}\n\n.mailharbor-markdown code {\n padding: 1px 4px;\n border-radius: 4px;\n background: var(--m-bg-2);\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 11.5px;\n}\n\n.mailharbor-markdown pre {\n margin: 0;\n padding: 10px 12px;\n border-radius: 8px;\n background: var(--m-bg);\n border: 1px solid var(--m-line-soft);\n overflow: auto;\n}\n\n.mailharbor-markdown pre code {\n padding: 0;\n background: transparent;\n}\n\n.mailharbor-markdown blockquote {\n margin: 0;\n padding-left: 10px;\n border-left: 2px solid var(--m-line);\n color: var(--m-text-2);\n}\n\n.mailharbor-markdown hr {\n width: 100%;\n margin: 4px 0;\n border: 0;\n border-top: 1px solid var(--m-line-soft);\n}\n\n/* ---------- composer AI ---------- */\n\n.mailharbor-toolbar button.ai {\n color: var(--m-accent);\n}\n\n.mailharbor-compose-foot {\n display: flex;\n align-items: center;\n gap: 10px;\n width: 100%;\n flex-wrap: wrap;\n}\n\n.mailharbor-versions {\n display: flex;\n align-items: center;\n gap: 6px;\n flex-wrap: wrap;\n padding-top: 8px;\n}\n\n.mailharbor-versions .chip {\n height: 22px;\n padding: 0 8px;\n border: 1px solid var(--m-line);\n border-radius: 999px;\n color: var(--m-text-3);\n font-size: 11px;\n}\n\n.mailharbor-versions .chip.active {\n background: var(--m-accent-soft);\n border-color: transparent;\n color: var(--m-text);\n}\n\n/* ---------- settings ---------- */\n\n.mailharbor.mailharbor-settings-page {\n height: auto;\n min-height: 100%;\n overflow: visible;\n}\n\n.mailharbor-settings {\n flex: 0 0 auto;\n width: 100%;\n max-width: 100%;\n min-width: 0;\n padding: 18px 20px 48px;\n display: flex;\n flex-direction: column;\n gap: 16px;\n overflow-x: hidden;\n}\n\n.mailharbor-settings > * {\n min-width: 0;\n}\n\n.mailharbor-settings h2 {\n margin: 0;\n font-size: 18px;\n font-weight: 600;\n letter-spacing: -0.01em;\n}\n\n.mailharbor-settings h3 {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--m-text-3);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n}\n\n.mailharbor-card {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 16px;\n border: 1px solid var(--m-line-soft);\n border-radius: 12px;\n background: var(--m-bg-1);\n min-width: 0;\n overflow: hidden;\n}\n\n.mailharbor-account-row {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n.mailharbor-account-row .meta {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n\n.mailharbor-account-row .meta strong {\n font-weight: 600;\n}\n\n.mailharbor-account-row .meta span {\n color: var(--m-text-3);\n font-size: 11.5px;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.mailharbor .avatar {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n border-radius: 50%;\n background: var(--m-accent-soft);\n color: var(--m-accent);\n font-weight: 600;\n}\n\n.mailharbor-log {\n padding: 10px 12px;\n border-radius: 8px;\n background: var(--m-bg);\n border: 1px solid var(--m-line-soft);\n color: var(--m-text-2);\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 11px;\n line-height: 1.6;\n max-height: 220px;\n overflow: auto;\n white-space: pre-wrap;\n}\n\n.mailharbor-kv {\n display: grid;\n grid-template-columns: minmax(120px, 170px) 1fr;\n gap: 6px 14px;\n font-size: 12px;\n margin: 0;\n}\n\n.mailharbor-kv dt {\n color: var(--m-text-3);\n}\n\n.mailharbor-kv dd {\n margin: 0;\n color: var(--m-text-2);\n word-break: break-all;\n}\n\n.mailharbor hr {\n border: 0;\n border-top: 1px solid var(--m-line-soft);\n margin: 2px 0;\n}\n\n/* ---------- conversation rows ---------- */\n\n.mailharbor-thread {\n display: flex;\n flex-direction: column;\n}\n\n.mailharbor-row.compact {\n padding-top: 6px;\n padding-bottom: 6px;\n padding-left: 34px;\n background: var(--m-bg);\n}\n\n.mailharbor-row.compact .line-1 {\n opacity: 0.85;\n}\n\n.mailharbor-row.compact .snippet {\n -webkit-line-clamp: 1;\n}\n\n.mailharbor .to-label {\n font-size: 10.5px;\n color: var(--m-text-3);\n flex: none;\n}\n\n.mailharbor .thread-count {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 18px;\n height: 18px;\n padding: 0 5px;\n border-radius: 999px;\n background: var(--m-bg-3);\n color: var(--m-text-2);\n font-size: 10.5px;\n flex: none;\n}\n\n.mailharbor .thread-count:hover {\n background: var(--m-accent-soft);\n color: var(--m-text);\n}\n\n.mailharbor-account {\n height: auto;\n padding: 3px 8px 3px 3px;\n border-radius: 8px;\n gap: 8px;\n}\n\n.mailharbor-account:hover {\n background: var(--m-hover);\n}\n\n/* ---------- mailbox sidebar ---------- */\n\n.mailharbor-side {\n display: flex;\n flex-direction: column;\n gap: 2px;\n width: 214px;\n flex: none;\n padding: 10px 8px;\n overflow: auto;\n border-right: 1px solid var(--m-line-soft);\n background: var(--m-bg);\n}\n\n.mailharbor-side-section {\n padding: 12px 8px 4px;\n font-size: 10.5px;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n color: var(--m-text-3);\n}\n\n.mailharbor-side-item {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n height: 32px;\n padding: 0 8px;\n border-radius: 8px;\n color: var(--m-text-2);\n font-size: 12.5px;\n text-align: left;\n flex: none;\n}\n\n.mailharbor-side-item:hover {\n background: var(--m-hover);\n}\n\n.mailharbor-side-item.active {\n background: var(--m-accent-soft);\n color: var(--m-text);\n}\n\n.mailharbor-side-item .name {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mailharbor-side-item .sub {\n display: block;\n font-size: 10.5px;\n color: var(--m-text-3);\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.mailharbor-side-item .count {\n font-size: 11px;\n color: var(--m-text-3);\n}\n\n.mailharbor-side-item svg {\n flex: none;\n color: var(--m-text-3);\n}\n\n.mailharbor-side-item.active svg {\n color: var(--m-accent);\n}\n\n@media (max-width: 900px) {\n .mailharbor-side {\n display: none;\n }\n}\n\n/* ---------- task board ---------- */\n\n.tasks-board-wrap {\n flex: 1;\n min-height: 0;\n overflow: auto;\n display: flex;\n flex-direction: column;\n gap: 14px;\n padding: 16px 18px 40px;\n}\n\n.tasks-summary {\n display: flex;\n align-items: center;\n gap: 20px;\n padding: 14px 16px;\n border: 1px solid var(--m-line-soft);\n border-radius: 12px;\n background: var(--m-bg-1);\n}\n\n.tasks-summary-main {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 7px;\n}\n\n.tasks-summary-labels {\n display: flex;\n align-items: baseline;\n gap: 10px;\n}\n\n.tasks-stats {\n display: flex;\n gap: 18px;\n}\n\n.tasks-stat {\n display: flex;\n flex-direction: column;\n gap: 1px;\n min-width: 42px;\n}\n\n.tasks-stat strong {\n font-size: 16px;\n font-weight: 600;\n color: var(--m-text);\n}\n\n.tasks-stat span {\n font-size: 11px;\n color: var(--m-text-3);\n}\n\n.tasks-stat.danger strong {\n color: var(--m-danger);\n}\n\n.tasks-toolbar {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n.tasks-board {\n display: grid;\n grid-template-columns: repeat(4, minmax(0, 1fr));\n gap: 14px;\n align-items: start;\n}\n\n.task-column {\n display: flex;\n flex-direction: column;\n gap: 8px;\n min-height: 140px;\n padding: 8px;\n border: 1px solid var(--m-line-soft);\n border-radius: 12px;\n background: var(--m-bg-1);\n}\n\n.task-column-head {\n display: flex;\n align-items: center;\n gap: 7px;\n padding: 2px 4px 6px;\n}\n\n.task-column-head strong {\n font-size: 12.5px;\n font-weight: 600;\n}\n\n.task-column-count {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 18px;\n height: 18px;\n padding: 0 5px;\n border-radius: 999px;\n background: var(--m-bg-3);\n color: var(--m-text-3);\n font-size: 10.5px;\n}\n\n.task-dot {\n width: 7px;\n height: 7px;\n border-radius: 999px;\n background: var(--m-text-3);\n}\n\n.task-dot.NEEDS-ACTION {\n background: #8a8f98;\n}\n\n.task-dot.IN-PROCESS {\n background: var(--m-accent);\n}\n\n.task-dot.COMPLETED {\n background: var(--m-ok);\n}\n\n.task-dot.CANCELLED {\n background: var(--m-danger);\n opacity: 0.7;\n}\n\n.task-column-body {\n display: flex;\n flex-direction: column;\n gap: 8px;\n min-height: 60px;\n}\n\n.task-column-empty {\n padding: 18px 8px;\n text-align: center;\n color: var(--m-text-3);\n font-size: 11.5px;\n border: 1px dashed var(--m-line-soft);\n border-radius: 9px;\n}\n\n.task-card {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 10px 11px;\n border: 1px solid var(--m-line-soft);\n border-radius: 10px;\n background: var(--m-bg);\n cursor: grab;\n transition:\n border-color 120ms ease,\n box-shadow 120ms ease;\n}\n\n.task-card:hover {\n border-color: var(--m-line);\n box-shadow: 0 4px 14px rgba(0, 0, 0, 0.16);\n}\n\n.task-card.dragging {\n opacity: 0.5;\n}\n\n.task-card-top {\n display: flex;\n align-items: flex-start;\n gap: 7px;\n}\n\n.task-card-title {\n flex: 1;\n min-width: 0;\n font-size: 12.5px;\n line-height: 1.45;\n color: var(--m-text);\n overflow-wrap: anywhere;\n}\n\n.task-priority {\n width: 3px;\n height: 14px;\n margin-top: 2px;\n border-radius: 999px;\n background: transparent;\n flex: none;\n}\n\n.task-priority.p5 {\n background: #d9a441;\n}\n\n.task-priority.p9 {\n background: var(--m-danger);\n}\n\n.task-card-delete {\n width: 18px;\n height: 18px;\n flex: none;\n border-radius: 5px;\n color: var(--m-text-3);\n opacity: 0;\n}\n\n.task-card:hover .task-card-delete {\n opacity: 1;\n}\n\n.task-card-notes {\n margin: 0;\n font-size: 11.5px;\n line-height: 1.55;\n color: var(--m-text-3);\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n.task-card-meta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n font-size: 10.5px;\n color: var(--m-text-3);\n}\n\n.task-due,\n.task-owner,\n.task-project,\n.task-priority-label {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n padding: 1px 6px;\n border-radius: 5px;\n background: var(--m-bg-2);\n}\n\n.task-due.overdue {\n color: var(--m-danger);\n background: rgba(242, 90, 90, 0.12);\n}\n\n.task-project {\n color: var(--m-accent);\n background: var(--m-accent-soft);\n}\n\n.task-source {\n align-self: flex-start;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n height: 22px;\n padding: 0 6px;\n border-radius: 5px;\n color: var(--m-text-3);\n font-size: 10.5px;\n}\n\n.task-source:hover {\n background: var(--m-hover);\n color: var(--m-text-2);\n}\n\n@media (max-width: 1180px) {\n .tasks-board {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n }\n}\n\n@media (max-width: 720px) {\n .tasks-board {\n grid-template-columns: minmax(0, 1fr);\n }\n}\n\n/* ---------- AI search ---------- */\n\n.mailharbor-ai-search {\n display: flex;\n align-items: center;\n gap: 8px;\n margin: 0 12px 8px;\n padding: 7px 10px;\n border-radius: 8px;\n background: var(--m-accent-soft);\n color: var(--m-text-2);\n font-size: 12px;\n}\n\n.mailharbor-ai-search.searching {\n background: var(--m-bg-2);\n color: var(--m-text-3);\n}\n\n/* ---------- task editor ---------- */\n\n.mailharbor-tasks .mailharbor-modal {\n max-width: 760px;\n}\n\n.task-editor {\n display: grid;\n grid-template-columns: minmax(0, 1fr) 230px;\n gap: 18px;\n}\n\n.task-editor-main {\n display: flex;\n flex-direction: column;\n gap: 14px;\n min-width: 0;\n}\n\n.task-editor-title input {\n width: 100%;\n height: 38px;\n font-size: 15px;\n font-weight: 500;\n}\n\n.task-editor-section {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.task-editor-section label,\n.task-editor-field label {\n font-size: 11px;\n color: var(--m-text-3);\n}\n\n.task-editor-section textarea {\n width: 100%;\n resize: vertical;\n}\n\n.task-editor-side {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 12px;\n border: 1px solid var(--m-line-soft);\n border-radius: 10px;\n background: var(--m-bg-1);\n align-self: start;\n}\n\n.task-editor-field {\n display: flex;\n flex-direction: column;\n gap: 5px;\n min-width: 0;\n}\n\n.task-editor-field input,\n.task-editor-field select {\n width: 100%;\n}\n\n.task-priority-picker {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 4px;\n padding: 3px;\n border: 1px solid var(--m-line);\n border-radius: 8px;\n background: var(--m-bg);\n}\n\n.task-priority-picker button {\n height: 26px;\n border-radius: 6px;\n font-size: 11.5px;\n color: var(--m-text-3);\n}\n\n.task-priority-picker button.active {\n background: var(--m-accent-soft);\n color: var(--m-text);\n}\n\n.task-editor-foot {\n display: flex;\n align-items: center;\n gap: 10px;\n width: 100%;\n}\n";
39
39
 
40
40
  // src/client/icon.tsx
41
41
  var import_react = require("react");
@@ -126,6 +126,10 @@ var api = {
126
126
  check: (id) => request(`accounts/${encodeURIComponent(id)}/check`, { method: "POST" }),
127
127
  aiStatus: (accountId) => request(`ai/status${query({ accountId: accountId ?? null })}`),
128
128
  aiModels: () => request("ai/models"),
129
+ aiSearch: (payload) => request("ai/search", {
130
+ method: "POST",
131
+ body: JSON.stringify(payload)
132
+ }),
129
133
  aiThread: (accountId, key) => request(`ai/thread${query({ accountId: accountId ?? null, key })}`),
130
134
  aiAddCard: (payload) => request("ai/cards", {
131
135
  method: "POST",
@@ -182,6 +186,7 @@ var api = {
182
186
  }),
183
187
  syncTasks: (accountId) => request("tasks/sync", { method: "POST", body: JSON.stringify({ accountId }) }),
184
188
  projects: () => request("projects"),
189
+ pickDirectory: () => request("projects/pick-directory", { method: "POST" }),
185
190
  projectFor: (accountId, key) => request(`projects/for${query({ accountId: accountId ?? null, key })}`),
186
191
  createProject: (payload) => request("projects", {
187
192
  method: "POST",
@@ -499,6 +504,12 @@ var PATHS = {
499
504
  folder: (0, import_react3.createElement)("path", {
500
505
  d: "M3.5 6.5A1.5 1.5 0 0 1 5 5h4l2 2.5h8A1.5 1.5 0 0 1 20.5 9v8.5A1.5 1.5 0 0 1 19 19H5a1.5 1.5 0 0 1-1.5-1.5z"
501
506
  }),
507
+ calendar: (0, import_react3.createElement)(
508
+ "g",
509
+ null,
510
+ (0, import_react3.createElement)("rect", { x: 3.5, y: 5, width: 17, height: 15.5, rx: 2 }),
511
+ (0, import_react3.createElement)("path", { d: "M3.5 9.5h17M8 3.5v3M16 3.5v3" })
512
+ ),
502
513
  checklist: (0, import_react3.createElement)(
503
514
  "g",
504
515
  null,
@@ -1096,10 +1107,28 @@ ${card.output}`
1096
1107
  "div",
1097
1108
  { className: "mailharbor-field" },
1098
1109
  (0, import_react5.createElement)("label", null, "\u9879\u76EE\u76EE\u5F55\uFF08\u9ED8\u8BA4\u53EF\u5728\u8BBE\u7F6E\u91CC\u6539\uFF09"),
1099
- (0, import_react5.createElement)("input", {
1100
- value: projectRoot,
1101
- onChange: (event) => setProjectRoot(event.target.value)
1102
- })
1110
+ (0, import_react5.createElement)(
1111
+ "div",
1112
+ { style: { display: "flex", gap: "6px" } },
1113
+ (0, import_react5.createElement)("input", {
1114
+ value: projectRoot,
1115
+ onChange: (event) => setProjectRoot(event.target.value)
1116
+ }),
1117
+ (0, import_react5.createElement)(
1118
+ "button",
1119
+ {
1120
+ className: "ghost",
1121
+ title: "\u4ECE\u6587\u4EF6\u7BA1\u7406\u5668\u9009\u62E9",
1122
+ onClick: () => {
1123
+ void api.pickDirectory().then((result) => {
1124
+ if (result.path !== null) setProjectRoot(result.path);
1125
+ }).catch((caught) => setProjectWarnings([errorText(caught)]));
1126
+ }
1127
+ },
1128
+ (0, import_react5.createElement)(Icon, { name: "folder", size: 13 }),
1129
+ "\u6D4F\u89C8\u2026"
1130
+ )
1131
+ )
1103
1132
  ),
1104
1133
  (0, import_react5.createElement)(
1105
1134
  "p",
@@ -2080,6 +2109,43 @@ function consumeOpenMail() {
2080
2109
  // src/client/panel.tsx
2081
2110
  var EMPTY_PAGE = { items: [], total: 0, offset: 0, limit: 40 };
2082
2111
  var DRAFTS = "__drafts";
2112
+ var folderCache = /* @__PURE__ */ new Map();
2113
+ var pageCache = /* @__PURE__ */ new Map();
2114
+ var FOLDER_LABELS = {
2115
+ INBOX: "\u6536\u4EF6\u7BB1",
2116
+ "Sent Items": "\u5DF2\u53D1\u9001",
2117
+ Sent: "\u5DF2\u53D1\u9001",
2118
+ Drafts: "\u8349\u7A3F",
2119
+ "Junk E-mail": "\u5783\u573E\u90AE\u4EF6",
2120
+ Junk: "\u5783\u573E\u90AE\u4EF6",
2121
+ Trash: "\u5DF2\u5220\u9664",
2122
+ Deleted: "\u5DF2\u5220\u9664",
2123
+ Archive: "\u5B58\u6863"
2124
+ };
2125
+ function folderName(name) {
2126
+ return FOLDER_LABELS[name] ?? name;
2127
+ }
2128
+ function isSentFolder(mailbox) {
2129
+ return /(^|[/.])(sent|sent items|sent mail|已发送|发件箱)/i.test(mailbox);
2130
+ }
2131
+ function normalizeSubject(subject) {
2132
+ return subject.replace(/^(\s*(re|fw|fwd|回复|转发)\s*[::]\s*)+/gi, "").replace(/\s+/g, " ").trim().toLowerCase();
2133
+ }
2134
+ function groupMessages(items) {
2135
+ const map = /* @__PURE__ */ new Map();
2136
+ const order = [];
2137
+ for (const item of items) {
2138
+ const key = normalizeSubject(item.subject) === "" ? `__${item.accountId}:${item.id}` : normalizeSubject(item.subject);
2139
+ const existing = map.get(key);
2140
+ if (existing === void 0) {
2141
+ map.set(key, [item]);
2142
+ order.push(key);
2143
+ } else {
2144
+ existing.push(item);
2145
+ }
2146
+ }
2147
+ return order.map((key) => ({ key, items: map.get(key) ?? [] }));
2148
+ }
2083
2149
  function errorText5(error) {
2084
2150
  return error instanceof ApiError || error instanceof Error ? error.message : String(error);
2085
2151
  }
@@ -2094,7 +2160,7 @@ function MailPanel() {
2094
2160
  const [state, setState] = (0, import_react9.useState)(null);
2095
2161
  const [error, setError] = (0, import_react9.useState)("");
2096
2162
  const [accountScope, setAccountScope] = (0, import_react9.useState)(null);
2097
- const [source, setSource] = (0, import_react9.useState)("local");
2163
+ const [source, setSource] = (0, import_react9.useState)("server");
2098
2164
  const [mailbox, setMailbox] = (0, import_react9.useState)("all");
2099
2165
  const [folders, setFolders] = (0, import_react9.useState)([]);
2100
2166
  const [query2, setQuery] = (0, import_react9.useState)("");
@@ -2112,11 +2178,21 @@ function MailPanel() {
2112
2178
  const [narrow, setNarrow] = (0, import_react9.useState)(() => matchesNarrow());
2113
2179
  const [railOpen, setRailOpen] = (0, import_react9.useState)(false);
2114
2180
  const [selectMode, setSelectMode] = (0, import_react9.useState)(false);
2181
+ const [sideOpen, setSideOpen] = (0, import_react9.useState)(() => {
2182
+ try {
2183
+ return window.localStorage.getItem("mailharbor.side") !== "0";
2184
+ } catch {
2185
+ return true;
2186
+ }
2187
+ });
2188
+ const [expandedGroups, setExpandedGroups] = (0, import_react9.useState)([]);
2115
2189
  const [selectedKeys, setSelectedKeys] = (0, import_react9.useState)([]);
2116
2190
  const [projectOptions, setProjectOptions] = (0, import_react9.useState)([]);
2117
2191
  const [targetProject, setTargetProject] = (0, import_react9.useState)("");
2118
2192
  const [extraction, setExtraction] = (0, import_react9.useState)(null);
2119
2193
  const [extractBusy, setExtractBusy] = (0, import_react9.useState)(false);
2194
+ const [aiInfo, setAiInfo] = (0, import_react9.useState)(null);
2195
+ const [aiSearching, setAiSearching] = (0, import_react9.useState)(false);
2120
2196
  const [aiRequest, setAiRequest] = (0, import_react9.useState)(null);
2121
2197
  const [tick, setTick] = (0, import_react9.useState)(0);
2122
2198
  const accounts = state?.accounts ?? [];
@@ -2161,6 +2237,44 @@ function MailPanel() {
2161
2237
  setError(errorText5(caught));
2162
2238
  }
2163
2239
  };
2240
+ const runAiSearch = async () => {
2241
+ const text = query2.trim();
2242
+ if (text === "" || accountScope === null) return;
2243
+ setAiSearching(true);
2244
+ setError("");
2245
+ try {
2246
+ const result = await Promise.race([
2247
+ api.aiSearch({ accountId: accountScope, source, mailbox, query: text }),
2248
+ new Promise((_resolve, reject) => {
2249
+ window.setTimeout(() => reject(new Error("AI \u641C\u7D22\u8D85\u65F6\uFF0C\u5DF2\u6539\u7528\u666E\u901A\u641C\u7D22")), 45e3);
2250
+ })
2251
+ ]);
2252
+ setPage({ items: result.items, total: result.total, offset: 0, limit: result.limit });
2253
+ setDrafts([]);
2254
+ setAiInfo(result.ai);
2255
+ setSearch("");
2256
+ } catch (caught) {
2257
+ setError(errorText5(caught));
2258
+ setAiInfo(null);
2259
+ setSearch(text);
2260
+ } finally {
2261
+ setAiSearching(false);
2262
+ }
2263
+ };
2264
+ const toggleSide = () => {
2265
+ setSideOpen((open) => {
2266
+ try {
2267
+ window.localStorage.setItem("mailharbor.side", open ? "0" : "1");
2268
+ } catch {
2269
+ }
2270
+ return !open;
2271
+ });
2272
+ };
2273
+ const toggleGroup = (key) => {
2274
+ setExpandedGroups(
2275
+ (current) => current.includes(key) ? current.filter((entry) => entry !== key) : [...current, key]
2276
+ );
2277
+ };
2164
2278
  const toggleSelect = (item) => {
2165
2279
  const key = `${item.accountId}:${item.id}`;
2166
2280
  setSelectedKeys(
@@ -2263,6 +2377,9 @@ function MailPanel() {
2263
2377
  (0, import_react9.useEffect)(() => {
2264
2378
  if (accountScope === null) return;
2265
2379
  let cancelled = false;
2380
+ const cacheKey = `${accountScope}:${source}`;
2381
+ const cached = folderCache.get(cacheKey);
2382
+ if (cached !== void 0) setFolders(cached);
2266
2383
  api.folders({ accountId: accountScope, source }).then((result) => {
2267
2384
  if (cancelled) return;
2268
2385
  const items2 = source === "local" ? [
@@ -2277,8 +2394,9 @@ function MailPanel() {
2277
2394
  }))
2278
2395
  ] : result.items.map((item) => ({ ...item, label: item.label ?? item.name }));
2279
2396
  setFolders(items2);
2397
+ folderCache.set(cacheKey, items2);
2280
2398
  }).catch(() => {
2281
- if (!cancelled) setFolders([]);
2399
+ if (!cancelled && cached === void 0) setFolders([]);
2282
2400
  });
2283
2401
  return () => {
2284
2402
  cancelled = true;
@@ -2287,11 +2405,15 @@ function MailPanel() {
2287
2405
  (0, import_react9.useEffect)(() => {
2288
2406
  setMailbox(source === "local" ? "all" : "INBOX");
2289
2407
  setSelected(null);
2408
+ setAiInfo(null);
2409
+ setSearch("");
2290
2410
  setLimit(state?.settings.render.listLimit ?? 40);
2291
2411
  }, [source]);
2292
2412
  (0, import_react9.useEffect)(() => {
2293
2413
  if (accountScope === "all" && source === "server") setSource("local");
2294
2414
  setSelected(null);
2415
+ setAiInfo(null);
2416
+ setSearch("");
2295
2417
  }, [accountScope, source]);
2296
2418
  (0, import_react9.useEffect)(() => {
2297
2419
  if (accountScope === null) return;
@@ -2308,9 +2430,9 @@ function MailPanel() {
2308
2430
  return [];
2309
2431
  }
2310
2432
  })
2311
- ).then((groups) => {
2433
+ ).then((groups2) => {
2312
2434
  if (cancelled) return;
2313
- setDrafts(groups.flat().sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)));
2435
+ setDrafts(groups2.flat().sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)));
2314
2436
  setPage(EMPTY_PAGE);
2315
2437
  }).finally(() => {
2316
2438
  if (!cancelled) setLoading(false);
@@ -2319,10 +2441,28 @@ function MailPanel() {
2319
2441
  cancelled = true;
2320
2442
  };
2321
2443
  }
2444
+ const pageKey = `${accountScope}:${source}:${mailbox}:${search}:${String(limit)}`;
2445
+ const cachedPage = pageCache.get(pageKey);
2446
+ if (cachedPage !== void 0) {
2447
+ setPage(cachedPage);
2448
+ setLoading(false);
2449
+ }
2322
2450
  api.messages({ accountId: accountScope, source, mailbox, query: search, limit }).then((result) => {
2323
2451
  if (!cancelled) {
2324
2452
  setPage(result);
2325
2453
  setDrafts([]);
2454
+ if (source === "server" && mailbox !== "all") {
2455
+ const update = (list) => list.map((folder) => folder.name === mailbox ? { ...folder, count: result.total } : folder);
2456
+ setFolders(update);
2457
+ const key = `${accountScope}:server`;
2458
+ const cachedFolders = folderCache.get(key);
2459
+ if (cachedFolders !== void 0) folderCache.set(key, update(cachedFolders));
2460
+ }
2461
+ pageCache.set(pageKey, result);
2462
+ if (pageCache.size > 12) {
2463
+ const oldest = pageCache.keys().next().value;
2464
+ if (oldest !== void 0) pageCache.delete(oldest);
2465
+ }
2326
2466
  }
2327
2467
  }).catch((caught) => {
2328
2468
  if (!cancelled) setError(errorText5(caught));
@@ -2510,18 +2650,11 @@ function MailPanel() {
2510
2650
  setError(errorText5(caught));
2511
2651
  }
2512
2652
  };
2513
- const folderOptions = (0, import_react9.useMemo)(
2514
- () => folders.map((folder) => ({
2515
- value: folder.name,
2516
- label: `${folder.label ?? folder.name}${folder.count > 0 ? ` (${String(folder.count)})` : ""}`
2517
- })),
2518
- [folders]
2519
- );
2520
2653
  if (state === null) {
2521
2654
  return (0, import_react9.createElement)(
2522
2655
  "div",
2523
2656
  { className: "mailharbor" },
2524
- (0, import_react9.createElement)("div", { className: "mailharbor-empty" }, error === "" ? "\u6B63\u5728\u52A0\u8F7D\u90AE\u7BB1\u2026" : error)
2657
+ (0, import_react9.createElement)("div", { className: "mailharbor-settings" }, "\u6B63\u5728\u8BFB\u53D6\u90AE\u7BB1\u2026")
2525
2658
  );
2526
2659
  }
2527
2660
  if (accounts.length === 0) {
@@ -2552,6 +2685,65 @@ function MailPanel() {
2552
2685
  const loggedIn = isAll ? accounts.some((item) => item.id === state.activeAccountId) : state.loggedIn;
2553
2686
  const needsLogin = source === "server" && !isAll && state.loggedIn === false;
2554
2687
  const items = page.items;
2688
+ const isSent = isSentFolder(mailbox);
2689
+ const groups = groupMessages(items);
2690
+ const renderMessage = (item, meta = {}) => {
2691
+ const sent = isSent && item.to.length > 0;
2692
+ const first = sent ? item.to[0] : item.from;
2693
+ const who = first === void 0 || first === null ? "" : first.name !== "" ? first.name : first.address;
2694
+ const extra = sent && item.to.length > 1 ? ` +${String(item.to.length - 1)}` : "";
2695
+ const isSelected = selected?.id === item.id && selected?.accountId === item.accountId;
2696
+ return (0, import_react9.createElement)(
2697
+ "div",
2698
+ {
2699
+ key: `${item.accountId}:${item.id}`,
2700
+ className: `mailharbor-row${item.unread ? " unread" : ""}${isSelected ? " selected" : ""}${selectMode ? " selectable" : ""}${meta.compact === true ? " compact" : ""}`,
2701
+ role: "button",
2702
+ tabIndex: 0,
2703
+ onClick: () => {
2704
+ if (selectMode) toggleSelect(item);
2705
+ else void openMessage(item);
2706
+ },
2707
+ onKeyDown: (event) => {
2708
+ if (event.key === "Enter" || event.key === " ") {
2709
+ event.preventDefault();
2710
+ if (selectMode) toggleSelect(item);
2711
+ else void openMessage(item);
2712
+ }
2713
+ }
2714
+ },
2715
+ (0, import_react9.createElement)(
2716
+ "span",
2717
+ { className: "line-1" },
2718
+ selectMode ? (0, import_react9.createElement)("input", {
2719
+ type: "checkbox",
2720
+ checked: selectedKeys.includes(`${item.accountId}:${item.id}`),
2721
+ "aria-label": "\u9009\u62E9\u90AE\u4EF6",
2722
+ onClick: (event) => event.stopPropagation(),
2723
+ onChange: () => toggleSelect(item)
2724
+ }) : item.unread ? (0, import_react9.createElement)("span", { className: "dot" }) : null,
2725
+ sent ? (0, import_react9.createElement)("span", { className: "to-label" }, "\u6536\u4EF6\u4EBA ") : null,
2726
+ (0, import_react9.createElement)("span", { className: "from" }, `${who}${extra}`),
2727
+ item.hasAttachments ? (0, import_react9.createElement)(Icon, { name: "paperclip", size: 13 }) : null,
2728
+ source === "local" && item.backedUp === false ? (0, import_react9.createElement)("span", { className: "badge warn" }, "\u672A\u5907\u4EFD") : null,
2729
+ meta.groupCount !== void 0 && meta.groupCount > 1 ? (0, import_react9.createElement)(
2730
+ "button",
2731
+ {
2732
+ className: "thread-count",
2733
+ title: meta.expanded === true ? "\u6536\u8D77\u4F1A\u8BDD" : `\u5C55\u5F00\u4F1A\u8BDD\uFF08${String(meta.groupCount)} \u5C01\uFF09`,
2734
+ onClick: (event) => {
2735
+ event.stopPropagation();
2736
+ toggleGroup(meta.groupKey ?? "");
2737
+ }
2738
+ },
2739
+ meta.expanded === true ? "\u2212" : String(meta.groupCount)
2740
+ ) : null,
2741
+ (0, import_react9.createElement)("span", { className: "date" }, shortDate(item.date))
2742
+ ),
2743
+ meta.compact === true ? null : (0, import_react9.createElement)("span", { className: "line-2" }, (0, import_react9.createElement)("span", { className: "subject" }, item.subject)),
2744
+ item.snippet === "" ? null : (0, import_react9.createElement)("span", { className: "snippet" }, item.snippet)
2745
+ );
2746
+ };
2555
2747
  return (0, import_react9.createElement)(
2556
2748
  "div",
2557
2749
  { className: narrow ? "mailharbor mailharbor-narrow" : "mailharbor" },
@@ -2559,21 +2751,21 @@ function MailPanel() {
2559
2751
  "div",
2560
2752
  { className: "mailharbor-topbar" },
2561
2753
  (0, import_react9.createElement)(
2562
- "div",
2563
- { className: "mailharbor-account" },
2564
- (0, import_react9.createElement)(Avatar, { email: isAll ? "all" : currentAccount?.email ?? "?", size: 26 }),
2565
- accounts.length > 1 ? (0, import_react9.createElement)(
2566
- "select",
2567
- {
2568
- value: accountScope ?? "",
2569
- onChange: (event) => void switchAccount(event.target.value),
2570
- "aria-label": "\u8D26\u53F7"
2571
- },
2572
- (0, import_react9.createElement)("option", { key: "all", value: "all" }, "\u5168\u90E8\u8D26\u53F7"),
2573
- accounts.map((item) => (0, import_react9.createElement)("option", { key: item.id, value: item.id }, item.label))
2574
- ) : (0, import_react9.createElement)("span", { className: "name" }, currentAccount?.label ?? ""),
2575
- isAll ? (0, import_react9.createElement)("span", { className: "badge" }, `${String(accounts.length)} \u4E2A\u8D26\u53F7`) : null,
2576
- !isAll && state.loggedIn === false ? (0, import_react9.createElement)("span", { className: "badge warn" }, "\u672A\u767B\u5F55") : null
2754
+ "button",
2755
+ {
2756
+ className: "mailharbor-account",
2757
+ onClick: toggleSide,
2758
+ title: sideOpen ? "\u6536\u8D77\u8D26\u53F7\u4E0E\u6587\u4EF6\u5939" : "\u5C55\u5F00\u8D26\u53F7\u4E0E\u6587\u4EF6\u5939"
2759
+ },
2760
+ (0, import_react9.createElement)(Avatar, { email: currentAccount?.email ?? "?", size: 26 }),
2761
+ (0, import_react9.createElement)(
2762
+ "div",
2763
+ { style: { display: "flex", flexDirection: "column", minWidth: 0 } },
2764
+ (0, import_react9.createElement)("span", { className: "name" }, currentAccount?.label ?? ""),
2765
+ (0, import_react9.createElement)("span", { className: "small muted" }, currentAccount?.email ?? "")
2766
+ ),
2767
+ state.loggedIn === false ? (0, import_react9.createElement)("span", { className: "badge warn" }, "\u672A\u767B\u5F55") : null,
2768
+ (0, import_react9.createElement)(Icon, { name: sideOpen ? "chevronLeft" : "chevronRight", size: 14 })
2577
2769
  ),
2578
2770
  (0, import_react9.createElement)(
2579
2771
  "div",
@@ -2601,16 +2793,6 @@ function MailPanel() {
2601
2793
  )
2602
2794
  ),
2603
2795
  (0, import_react9.createElement)("span", { className: "spacer" }),
2604
- (0, import_react9.createElement)(
2605
- "button",
2606
- {
2607
- className: "ghost icon",
2608
- title: "\u6DFB\u52A0\u8D26\u53F7",
2609
- "aria-label": "\u6DFB\u52A0\u8D26\u53F7",
2610
- onClick: () => setShowAdd(true)
2611
- },
2612
- (0, import_react9.createElement)(Icon, { name: "plus" })
2613
- ),
2614
2796
  (0, import_react9.createElement)(
2615
2797
  "button",
2616
2798
  { className: "ghost icon", title: "\u5237\u65B0\u5217\u8868", "aria-label": "\u5237\u65B0\u5217\u8868", onClick: refresh },
@@ -2652,37 +2834,75 @@ function MailPanel() {
2652
2834
  ) : (0, import_react9.createElement)(
2653
2835
  "div",
2654
2836
  { className: "mailharbor-body", "data-mode": narrow && selected !== null ? "reader" : "list" },
2837
+ sideOpen ? (0, import_react9.createElement)(
2838
+ "aside",
2839
+ { className: "mailharbor-side" },
2840
+ (0, import_react9.createElement)("div", { className: "mailharbor-side-section" }, "\u8D26\u53F7"),
2841
+ accounts.map(
2842
+ (item) => (0, import_react9.createElement)(
2843
+ "button",
2844
+ {
2845
+ key: item.id,
2846
+ className: `mailharbor-side-item${!isAll && item.id === accountScope ? " active" : ""}`,
2847
+ onClick: () => {
2848
+ void switchAccount(item.id);
2849
+ setSource("server");
2850
+ setMailbox("INBOX");
2851
+ }
2852
+ },
2853
+ (0, import_react9.createElement)(Avatar, { email: item.email, size: 18 }),
2854
+ (0, import_react9.createElement)(
2855
+ "span",
2856
+ { className: "name" },
2857
+ (0, import_react9.createElement)("span", null, item.label),
2858
+ (0, import_react9.createElement)("span", { className: "sub" }, item.email)
2859
+ ),
2860
+ item.enabled === false ? (0, import_react9.createElement)("span", { className: "badge warn" }, "\u505C\u7528") : null
2861
+ )
2862
+ ),
2863
+ (0, import_react9.createElement)(
2864
+ "div",
2865
+ { className: "mailharbor-side-section" },
2866
+ source === "server" ? "\u670D\u52A1\u5668\u6587\u4EF6\u5939" : "\u672C\u5730\u6587\u4EF6\u5939"
2867
+ ),
2868
+ folders.map(
2869
+ (folder) => (0, import_react9.createElement)(
2870
+ "button",
2871
+ {
2872
+ key: folder.name,
2873
+ className: `mailharbor-side-item${mailbox === folder.name ? " active" : ""}`,
2874
+ onClick: () => {
2875
+ setMailbox(folder.name);
2876
+ setSelected(null);
2877
+ }
2878
+ },
2879
+ (0, import_react9.createElement)(Icon, { name: folder.name === DRAFTS ? "compose" : "folder", size: 14 }),
2880
+ (0, import_react9.createElement)("span", { className: "name" }, folderName(folder.label ?? folder.name)),
2881
+ folder.count > 0 ? (0, import_react9.createElement)("span", { className: "count" }, String(folder.count)) : null
2882
+ )
2883
+ )
2884
+ ) : null,
2655
2885
  (0, import_react9.createElement)(
2656
2886
  "div",
2657
2887
  { className: "mailharbor-list" },
2658
2888
  (0, import_react9.createElement)(
2659
2889
  "div",
2660
2890
  { className: "mailharbor-filters" },
2661
- (0, import_react9.createElement)(
2662
- "select",
2663
- {
2664
- value: mailbox,
2665
- onChange: (event) => {
2666
- setMailbox(event.target.value);
2667
- setSelected(null);
2668
- },
2669
- "aria-label": "\u90AE\u7BB1\u6587\u4EF6\u5939"
2670
- },
2671
- folderOptions.map(
2672
- (option) => (0, import_react9.createElement)("option", { key: option.value, value: option.value }, option.label)
2673
- )
2674
- ),
2675
2891
  (0, import_react9.createElement)("input", {
2676
2892
  type: "search",
2677
- placeholder: showingDrafts ? "\u641C\u7D22\u8349\u7A3F" : "\u641C\u7D22\u90AE\u4EF6",
2893
+ placeholder: showingDrafts ? "\u641C\u7D22\u8349\u7A3F" : aiSearching ? "AI \u6B63\u5728\u7406\u89E3\u4F60\u7684\u641C\u7D22\u2026" : "\u641C\u7D22\u90AE\u4EF6\uFF0C\u6216\u63CF\u8FF0\u4F60\u8981\u627E\u4EC0\u4E48\uFF08AI\uFF09",
2678
2894
  "aria-label": "\u641C\u7D22\u90AE\u4EF6",
2679
2895
  value: query2,
2680
2896
  onChange: (event) => setQuery(event.target.value),
2681
2897
  onKeyDown: (event) => {
2682
- if (event.key === "Enter") setSearch(query2);
2898
+ if (event.key === "Enter") {
2899
+ if (showingDrafts) setSearch(query2);
2900
+ else void runAiSearch();
2901
+ }
2683
2902
  if (event.key === "Escape") {
2684
2903
  setQuery("");
2685
2904
  setSearch("");
2905
+ setAiInfo(null);
2686
2906
  }
2687
2907
  }
2688
2908
  }),
@@ -2711,11 +2931,35 @@ function MailPanel() {
2711
2931
  onClick: () => {
2712
2932
  setQuery("");
2713
2933
  setSearch("");
2934
+ setAiInfo(null);
2714
2935
  }
2715
2936
  },
2716
2937
  (0, import_react9.createElement)(Icon, { name: "close", size: 14 })
2717
2938
  ) : null
2718
2939
  ),
2940
+ aiInfo === null ? null : (0, import_react9.createElement)(
2941
+ "div",
2942
+ { className: "mailharbor-ai-search" },
2943
+ (0, import_react9.createElement)(Icon, { name: "sparkles", size: 13 }),
2944
+ (0, import_react9.createElement)(
2945
+ "span",
2946
+ null,
2947
+ aiInfo.explanation !== "" ? aiInfo.explanation : `\u5173\u952E\u8BCD\uFF1A${aiInfo.keywords.join("\u3001")}`
2948
+ ),
2949
+ (0, import_react9.createElement)("span", { className: "spacer" }),
2950
+ (0, import_react9.createElement)(
2951
+ "button",
2952
+ {
2953
+ className: "subtle",
2954
+ onClick: () => {
2955
+ setAiInfo(null);
2956
+ setTick((value) => value + 1);
2957
+ }
2958
+ },
2959
+ "\u6E05\u9664"
2960
+ )
2961
+ ),
2962
+ aiSearching ? (0, import_react9.createElement)("div", { className: "mailharbor-ai-search searching" }, "AI \u6B63\u5728\u7406\u89E3\u5E76\u641C\u7D22\u2026") : null,
2719
2963
  selectMode ? (0, import_react9.createElement)(
2720
2964
  "div",
2721
2965
  { className: "mailharbor-selectbar" },
@@ -2826,76 +3070,39 @@ function MailPanel() {
2826
3070
  ) : items.length === 0 ? (0, import_react9.createElement)(
2827
3071
  "div",
2828
3072
  { className: "mailharbor-empty" },
2829
- (0, import_react9.createElement)(Icon, { name: "mail", size: 28 }),
3073
+ (0, import_react9.createElement)(Icon, { name: aiInfo === null ? "mail" : "sparkles", size: 28 }),
2830
3074
  (0, import_react9.createElement)(
2831
3075
  "div",
2832
3076
  { className: "title" },
2833
- source === "local" ? "\u672C\u5730\u5907\u4EFD\u91CC\u8FD8\u6CA1\u6709\u90AE\u4EF6" : "\u670D\u52A1\u5668\u4E0A\u6CA1\u6709\u90AE\u4EF6"
3077
+ aiInfo === null ? source === "local" ? "\u672C\u5730\u5907\u4EFD\u91CC\u8FD8\u6CA1\u6709\u90AE\u4EF6" : "\u670D\u52A1\u5668\u4E0A\u6CA1\u6709\u90AE\u4EF6" : "AI \u641C\u7D22\u6CA1\u6709\u627E\u5230\u5339\u914D\u90AE\u4EF6"
2834
3078
  ),
2835
3079
  (0, import_react9.createElement)(
2836
3080
  "div",
2837
3081
  { className: "small" },
2838
- source === "local" ? "\u70B9\u9876\u90E8\u300C\u5907\u4EFD\u300D\u628A\u670D\u52A1\u5668\u90AE\u4EF6\u540C\u6B65\u4E0B\u6765\u3002" : "\u6362\u4E00\u4E2A\u6587\u4EF6\u5939\u6216\u7A0D\u540E\u5237\u65B0\u3002"
3082
+ aiInfo === null ? source === "local" ? "\u70B9\u9876\u90E8\u300C\u5907\u4EFD\u300D\u628A\u670D\u52A1\u5668\u90AE\u4EF6\u540C\u6B65\u4E0B\u6765\u3002" : "\u6362\u4E00\u4E2A\u6587\u4EF6\u5939\u6216\u7A0D\u540E\u5237\u65B0\u3002" : "\u6362\u4E2A\u8BF4\u6CD5\uFF0C\u6216\u76F4\u63A5\u8F93\u5165\u5173\u952E\u8BCD\u540E\u6309\u56DE\u8F66\u3002"
2839
3083
  )
2840
- ) : items.map(
2841
- (item) => (0, import_react9.createElement)(
3084
+ ) : selectMode ? items.map((item) => renderMessage(item)) : groups.map((group) => {
3085
+ const latest = group.items[0];
3086
+ if (latest === void 0) return null;
3087
+ const expanded = expandedGroups.includes(group.key);
3088
+ return (0, import_react9.createElement)(
2842
3089
  "div",
2843
- {
2844
- key: `${item.accountId}:${item.id}`,
2845
- className: `mailharbor-row${item.unread ? " unread" : ""}${selected?.id === item.id && selected?.accountId === item.accountId ? " selected" : ""}${selectMode ? " selectable" : ""}`,
2846
- role: "button",
2847
- tabIndex: 0,
2848
- onClick: () => {
2849
- if (selectMode) toggleSelect(item);
2850
- else void openMessage(item);
2851
- },
2852
- onKeyDown: (event) => {
2853
- if (event.key === "Enter" || event.key === " ") {
2854
- event.preventDefault();
2855
- if (selectMode) toggleSelect(item);
2856
- else void openMessage(item);
2857
- }
2858
- }
2859
- },
2860
- (0, import_react9.createElement)(
2861
- "span",
2862
- { className: "line-1" },
2863
- selectMode ? (0, import_react9.createElement)("input", {
2864
- type: "checkbox",
2865
- checked: selectedKeys.includes(`${item.accountId}:${item.id}`),
2866
- "aria-label": "\u9009\u62E9\u90AE\u4EF6",
2867
- onClick: (event) => event.stopPropagation(),
2868
- onChange: () => toggleSelect(item)
2869
- }) : item.unread ? (0, import_react9.createElement)("span", { className: "dot" }) : null,
2870
- (0, import_react9.createElement)(
2871
- "span",
2872
- { className: "from" },
2873
- item.from.name !== "" ? item.from.name : item.from.address
2874
- ),
2875
- item.hasAttachments ? (0, import_react9.createElement)(Icon, { name: "paperclip", size: 13 }) : null,
2876
- isAll ? (0, import_react9.createElement)(
2877
- "span",
2878
- { className: "badge" },
2879
- accounts.find((entry) => entry.id === item.accountId)?.label ?? ""
2880
- ) : null,
2881
- item.backedUp === false ? (0, import_react9.createElement)("span", { className: "badge warn" }, "\u672A\u5907\u4EFD") : null,
2882
- (0, import_react9.createElement)("span", { className: "date" }, shortDate(item.date))
2883
- ),
2884
- (0, import_react9.createElement)(
2885
- "span",
2886
- { className: "line-2" },
2887
- (0, import_react9.createElement)("span", { className: "subject" }, item.subject)
2888
- ),
2889
- item.snippet === "" ? null : (0, import_react9.createElement)("span", { className: "snippet" }, item.snippet)
2890
- )
2891
- )
3090
+ { className: "mailharbor-thread", key: group.key },
3091
+ renderMessage(latest, {
3092
+ groupCount: group.items.length,
3093
+ groupKey: group.key,
3094
+ expanded
3095
+ }),
3096
+ expanded ? group.items.slice(1).map((item) => renderMessage(item, { compact: true })) : null
3097
+ );
3098
+ })
2892
3099
  ),
2893
3100
  !showingDrafts && items.length > 0 ? (0, import_react9.createElement)(
2894
3101
  "div",
2895
3102
  { className: "mailharbor-list-foot" },
2896
3103
  (0, import_react9.createElement)("span", null, `${String(items.length)} / ${String(page.total)} \u5C01`),
2897
3104
  (0, import_react9.createElement)("span", { className: "spacer" }),
2898
- page.total > items.length ? (0, import_react9.createElement)(
3105
+ page.total > items.length && aiInfo === null ? (0, import_react9.createElement)(
2899
3106
  "button",
2900
3107
  { className: "subtle", onClick: () => setLimit((current) => current + 40) },
2901
3108
  "\u52A0\u8F7D\u66F4\u591A"
@@ -3062,15 +3269,6 @@ function MailSettings() {
3062
3269
  const [pollSeconds, setPollSeconds] = (0, import_react10.useState)(60);
3063
3270
  const [listLimit, setListLimit] = (0, import_react10.useState)(40);
3064
3271
  const [projectRoot, setProjectRoot] = (0, import_react10.useState)("");
3065
- const [caldavAccountId, setCaldavAccountId] = (0, import_react10.useState)("");
3066
- const [caldavUrl, setCaldavUrl] = (0, import_react10.useState)("");
3067
- const [caldavUser, setCaldavUser] = (0, import_react10.useState)("");
3068
- const [caldavPassword, setCaldavPassword] = (0, import_react10.useState)("");
3069
- const [caldavHref, setCaldavHref] = (0, import_react10.useState)("");
3070
- const [caldavHasPassword, setCaldavHasPassword] = (0, import_react10.useState)(false);
3071
- const [caldavCalendar, setCaldavCalendar] = (0, import_react10.useState)(null);
3072
- const [caldavMessage, setCaldavMessage] = (0, import_react10.useState)("");
3073
- const [caldavBusy, setCaldavBusy] = (0, import_react10.useState)(false);
3074
3272
  const [aiEnabled, setAiEnabled] = (0, import_react10.useState)(true);
3075
3273
  const [aiProvider, setAiProvider] = (0, import_react10.useState)("deepseek-account");
3076
3274
  const [aiModel, setAiModel] = (0, import_react10.useState)("deepseek-flash");
@@ -3094,9 +3292,6 @@ function MailSettings() {
3094
3292
  setPollSeconds(view.read.pollSeconds);
3095
3293
  setListLimit(view.render.listLimit);
3096
3294
  setProjectRoot(view.projects.root);
3097
- setCaldavAccountId(
3098
- (current) => current || view.activeAccountId || appState.settings.activeAccountId || ""
3099
- );
3100
3295
  setAiEnabled(view.ai.enabled);
3101
3296
  setAiProvider(view.ai.provider);
3102
3297
  setAiModel(view.ai.model);
@@ -3107,23 +3302,6 @@ function MailSettings() {
3107
3302
  cancelled = true;
3108
3303
  };
3109
3304
  }, [tick]);
3110
- (0, import_react10.useEffect)(() => {
3111
- let cancelled = false;
3112
- if (caldavAccountId === "") return;
3113
- api.taskCaldav(caldavAccountId).then((info) => {
3114
- if (cancelled) return;
3115
- setCaldavUrl(info.caldav?.url ?? "");
3116
- setCaldavUser(info.caldav?.username ?? "");
3117
- setCaldavHref(info.caldav?.calendarHref ?? "");
3118
- setCaldavHasPassword(info.hasPassword);
3119
- setCaldavCalendar(info.calendar);
3120
- }).catch((caught) => {
3121
- if (!cancelled) setCaldavMessage(errorText6(caught));
3122
- });
3123
- return () => {
3124
- cancelled = true;
3125
- };
3126
- }, [caldavAccountId, tick]);
3127
3305
  (0, import_react10.useEffect)(() => {
3128
3306
  let cancelled = false;
3129
3307
  const accountId = settings?.activeAccountId ?? state?.activeAccountId ?? null;
@@ -3309,147 +3487,29 @@ function MailSettings() {
3309
3487
  })
3310
3488
  ),
3311
3489
  (0, import_react10.createElement)(
3312
- "button",
3313
- {
3314
- className: "ghost small",
3315
- style: { alignSelf: "end" },
3316
- onClick: () => void saveGeneral({ projects: { root: projectRoot } })
3317
- },
3318
- "\u4FDD\u5B58\u76EE\u5F55"
3319
- )
3320
- )
3321
- ),
3322
- (0, import_react10.createElement)(
3323
- "div",
3324
- { className: "mailharbor-card" },
3325
- (0, import_react10.createElement)("h3", null, "\u4EFB\u52A1\u4E0E CalDAV"),
3326
- (0, import_react10.createElement)(
3327
- "p",
3328
- { className: "small muted" },
3329
- "\u4EFB\u52A1\u9762\u677F\u7684\u300C\u540C\u6B65 CalDAV\u300D\u4F7F\u7528\u8FD9\u91CC\u7684\u914D\u7F6E\u3002\u5BC6\u7801\u53EA\u5B58\u7CFB\u7EDF Keychain\uFF0C\u4E0D\u5199\u5165\u914D\u7F6E\u6587\u4EF6\u3002"
3330
- ),
3331
- (0, import_react10.createElement)(
3332
- "div",
3333
- { className: "mailharbor-grid" },
3334
- (0, import_react10.createElement)(
3335
- Field,
3336
- { label: "\u8D26\u53F7" },
3490
+ "div",
3491
+ { style: { display: "flex", gap: "6px", alignSelf: "end" } },
3337
3492
  (0, import_react10.createElement)(
3338
- "select",
3493
+ "button",
3339
3494
  {
3340
- value: caldavAccountId,
3341
- onChange: (event) => {
3342
- setCaldavAccountId(event.target.value);
3343
- setCaldavMessage("");
3495
+ className: "ghost small",
3496
+ onClick: () => {
3497
+ void api.pickDirectory().then((result) => {
3498
+ if (result.path !== null) setProjectRoot(result.path);
3499
+ }).catch((caught) => setError(errorText6(caught)));
3344
3500
  }
3345
3501
  },
3346
- (settings?.accounts ?? []).map(
3347
- (item) => (0, import_react10.createElement)("option", { key: item.id, value: item.id }, `${item.label}\uFF08${item.email}\uFF09`)
3348
- )
3349
- )
3350
- ),
3351
- (0, import_react10.createElement)(
3352
- Field,
3353
- { label: "\u72B6\u6001" },
3502
+ "\u6D4F\u89C8\u2026"
3503
+ ),
3354
3504
  (0, import_react10.createElement)(
3355
- "span",
3356
- { className: "small", style: { paddingTop: "7px", color: "var(--m-text-3)" } },
3357
- caldavCalendar === null ? "\u672A\u540C\u6B65" : caldavCalendar.lastSync === "" ? "\u672A\u540C\u6B65" : `\u4E0A\u6B21\u540C\u6B65 ${new Date(caldavCalendar.lastSync).toLocaleString()}`,
3358
- caldavCalendar?.error ? ` \xB7 \u5931\u8D25\uFF1A${caldavCalendar.error}` : ""
3505
+ "button",
3506
+ {
3507
+ className: "ghost small",
3508
+ onClick: () => void saveGeneral({ projects: { root: projectRoot } })
3509
+ },
3510
+ "\u4FDD\u5B58\u76EE\u5F55"
3359
3511
  )
3360
3512
  )
3361
- ),
3362
- (0, import_react10.createElement)(
3363
- "div",
3364
- { className: "mailharbor-grid" },
3365
- (0, import_react10.createElement)(
3366
- Field,
3367
- { label: "CalDAV \u5730\u5740" },
3368
- (0, import_react10.createElement)("input", {
3369
- value: caldavUrl,
3370
- placeholder: "https://caldav.example.com/",
3371
- onChange: (event) => setCaldavUrl(event.target.value)
3372
- })
3373
- ),
3374
- (0, import_react10.createElement)(
3375
- Field,
3376
- { label: "\u7528\u6237\u540D" },
3377
- (0, import_react10.createElement)("input", {
3378
- value: caldavUser,
3379
- placeholder: "\u901A\u5E38\u662F\u90AE\u7BB1\u5730\u5740",
3380
- onChange: (event) => setCaldavUser(event.target.value)
3381
- })
3382
- )
3383
- ),
3384
- (0, import_react10.createElement)(
3385
- "div",
3386
- { className: "mailharbor-grid" },
3387
- (0, import_react10.createElement)(
3388
- Field,
3389
- { label: caldavHasPassword ? "\u5E94\u7528\u5BC6\u7801\uFF08\u7559\u7A7A\u8868\u793A\u4E0D\u4FEE\u6539\uFF09" : "\u5E94\u7528\u5BC6\u7801" },
3390
- (0, import_react10.createElement)("input", {
3391
- type: "password",
3392
- value: caldavPassword,
3393
- placeholder: caldavHasPassword ? "\u5DF2\u4FDD\u5B58" : "iCloud / Fastmail \u7B49\u5E94\u7528\u4E13\u7528\u5BC6\u7801",
3394
- onChange: (event) => setCaldavPassword(event.target.value)
3395
- })
3396
- ),
3397
- (0, import_react10.createElement)(
3398
- Field,
3399
- { label: "\u4EFB\u52A1\u65E5\u5386\u8DEF\u5F84\uFF08\u53EF\u9009\uFF09" },
3400
- (0, import_react10.createElement)("input", {
3401
- value: caldavHref,
3402
- placeholder: "\u7559\u7A7A\u81EA\u52A8\u53D1\u73B0",
3403
- onChange: (event) => setCaldavHref(event.target.value)
3404
- })
3405
- )
3406
- ),
3407
- (0, import_react10.createElement)(
3408
- "div",
3409
- { style: { display: "flex", gap: "8px", alignItems: "center", flexWrap: "wrap" } },
3410
- (0, import_react10.createElement)(
3411
- "button",
3412
- {
3413
- className: "primary small",
3414
- disabled: caldavBusy,
3415
- onClick: () => {
3416
- setCaldavBusy(true);
3417
- setCaldavMessage("\u4FDD\u5B58\u4E2D\u2026");
3418
- void api.saveTaskCaldav({
3419
- accountId: caldavAccountId,
3420
- url: caldavUrl,
3421
- username: caldavUser,
3422
- ...caldavPassword === "" ? {} : { password: caldavPassword },
3423
- calendarHref: caldavHref
3424
- }).then((info) => {
3425
- setCaldavHasPassword(info.hasPassword);
3426
- setCaldavPassword("");
3427
- setCaldavMessage(info.hasPassword ? "\u5DF2\u4FDD\u5B58" : "\u5DF2\u4FDD\u5B58\u5730\u5740\uFF0C\u4F46\u8FD8\u6CA1\u6709\u5BC6\u7801");
3428
- setTick((value) => value + 1);
3429
- }).catch((caught) => setCaldavMessage(errorText6(caught))).finally(() => setCaldavBusy(false));
3430
- }
3431
- },
3432
- caldavBusy ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58 CalDAV"
3433
- ),
3434
- (0, import_react10.createElement)(
3435
- "button",
3436
- {
3437
- className: "ghost small",
3438
- disabled: caldavBusy,
3439
- onClick: () => {
3440
- setCaldavBusy(true);
3441
- setCaldavMessage("\u540C\u6B65\u4E2D\u2026");
3442
- void api.syncTasks(caldavAccountId).then((result) => {
3443
- setCaldavMessage(
3444
- `\u540C\u6B65\u5B8C\u6210\uFF1A\u62C9\u53D6 ${String(result.result.pulled)}\uFF0C\u63A8\u9001 ${String(result.result.pushed)}`
3445
- );
3446
- setTick((value) => value + 1);
3447
- }).catch((caught) => setCaldavMessage(errorText6(caught))).finally(() => setCaldavBusy(false));
3448
- }
3449
- },
3450
- "\u7ACB\u5373\u540C\u6B65"
3451
- ),
3452
- (0, import_react10.createElement)("span", { className: "small muted" }, caldavMessage)
3453
3513
  )
3454
3514
  ),
3455
3515
  (0, import_react10.createElement)(
@@ -3858,19 +3918,18 @@ function AccountRow({
3858
3918
  // src/client/tasks-panel.tsx
3859
3919
  var import_react11 = require("react");
3860
3920
  var FILTERS = [
3861
- { id: "open", label: "\u5F85\u529E" },
3921
+ { id: "all", label: "\u5168\u90E8" },
3862
3922
  { id: "today", label: "\u4ECA\u5929" },
3863
3923
  { id: "week", label: "\u672C\u5468" },
3864
- { id: "overdue", label: "\u903E\u671F" },
3865
- { id: "done", label: "\u5DF2\u5B8C\u6210" },
3866
- { id: "all", label: "\u5168\u90E8" }
3924
+ { id: "overdue", label: "\u903E\u671F" }
3867
3925
  ];
3868
- var STATUS_LABEL = {
3869
- "NEEDS-ACTION": "\u5F85\u5904\u7406",
3870
- "IN-PROCESS": "\u8FDB\u884C\u4E2D",
3871
- COMPLETED: "\u5DF2\u5B8C\u6210",
3872
- CANCELLED: "\u5DF2\u53D6\u6D88"
3873
- };
3926
+ var COLUMNS = [
3927
+ { status: "NEEDS-ACTION", label: "\u5F85\u5904\u7406", hint: "\u8FD8\u6CA1\u5F00\u59CB" },
3928
+ { status: "IN-PROCESS", label: "\u8FDB\u884C\u4E2D", hint: "\u6B63\u5728\u505A" },
3929
+ { status: "COMPLETED", label: "\u5DF2\u5B8C\u6210", hint: "\u6536\u5C3E" },
3930
+ { status: "CANCELLED", label: "\u7EC8\u6B62", hint: "\u4E0D\u505A\u4E86" }
3931
+ ];
3932
+ var PRIORITY_LABEL = { 0: "\u65E0", 5: "\u4E2D", 9: "\u9AD8" };
3874
3933
  function errorText7(error) {
3875
3934
  return error instanceof ApiError || error instanceof Error ? error.message : String(error);
3876
3935
  }
@@ -3886,11 +3945,13 @@ function weekEnd() {
3886
3945
  }
3887
3946
  function dueText(due) {
3888
3947
  if (due === "") return "";
3948
+ const value = due.slice(0, 10);
3889
3949
  const today = dateKey(/* @__PURE__ */ new Date());
3890
- if (due.slice(0, 10) < today) return `\u903E\u671F ${due.slice(0, 10)}`;
3891
- return due.slice(0, 10);
3950
+ if (value < today) return `\u903E\u671F ${value}`;
3951
+ if (value === today) return "\u4ECA\u5929";
3952
+ return value;
3892
3953
  }
3893
- var EMPTY_TASK = {
3954
+ var EMPTY = {
3894
3955
  title: "",
3895
3956
  notes: "",
3896
3957
  due: "",
@@ -3971,13 +4032,14 @@ function TasksPanel() {
3971
4032
  const [state, setState] = (0, import_react11.useState)(null);
3972
4033
  const [tasks, setTasks] = (0, import_react11.useState)([]);
3973
4034
  const [projects, setProjects] = (0, import_react11.useState)([]);
3974
- const [calendar, setCalendar] = (0, import_react11.useState)(null);
3975
- const [filter, setFilter] = (0, import_react11.useState)("open");
4035
+ const [filter, setFilter] = (0, import_react11.useState)("all");
4036
+ const [projectFilter, setProjectFilter] = (0, import_react11.useState)("");
3976
4037
  const [error, setError] = (0, import_react11.useState)("");
3977
4038
  const [toast, setToast] = (0, import_react11.useState)("");
3978
4039
  const [busy, setBusy] = (0, import_react11.useState)(false);
3979
4040
  const [editing, setEditing] = (0, import_react11.useState)(null);
3980
- const [creating, setCreating] = (0, import_react11.useState)(false);
4041
+ const [creating, setCreating] = (0, import_react11.useState)(null);
4042
+ const [dragging, setDragging] = (0, import_react11.useState)(null);
3981
4043
  const [tick, setTick] = (0, import_react11.useState)(0);
3982
4044
  const accountId = state?.settings.activeAccountId ?? null;
3983
4045
  (0, import_react11.useEffect)(() => {
@@ -3989,7 +4051,6 @@ function TasksPanel() {
3989
4051
  Promise.all([api.tasks(accountId), api.projects()]).then(([list, projectList]) => {
3990
4052
  if (cancelled) return;
3991
4053
  setTasks(list.items);
3992
- setCalendar(list.calendar);
3993
4054
  setProjects(projectList.items);
3994
4055
  }).catch((caught) => {
3995
4056
  if (!cancelled) setError(errorText7(caught));
@@ -4007,38 +4068,17 @@ function TasksPanel() {
4007
4068
  setToast("");
4008
4069
  }, 3e3);
4009
4070
  };
4010
- const visible = (0, import_react11.useMemo)(() => {
4011
- const today = dateKey(/* @__PURE__ */ new Date());
4012
- const end = weekEnd();
4013
- return tasks.filter((task) => {
4014
- const done2 = task.status === "COMPLETED" || task.status === "CANCELLED";
4015
- if (filter === "done") return done2;
4016
- if (done2) return false;
4017
- if (filter === "all") return true;
4071
+ const matchesFilter = (0, import_react11.useCallback)(
4072
+ (task) => {
4073
+ const today = dateKey(/* @__PURE__ */ new Date());
4074
+ const end = weekEnd();
4018
4075
  if (filter === "overdue") return task.due !== "" && task.due.slice(0, 10) < today;
4019
4076
  if (filter === "today") return task.due.slice(0, 10) === today;
4020
4077
  if (filter === "week") return task.due !== "" && task.due.slice(0, 10) <= end;
4021
4078
  return true;
4022
- });
4023
- }, [tasks, filter]);
4024
- const open = tasks.filter((task) => task.status !== "COMPLETED" && task.status !== "CANCELLED");
4025
- const done = tasks.length - open.length;
4026
- const progress = tasks.length === 0 ? 0 : Math.round(done / tasks.length * 100);
4027
- const byProject = (0, import_react11.useMemo)(() => {
4028
- const groups = /* @__PURE__ */ new Map();
4029
- for (const task of tasks) {
4030
- const key = task.projectId ?? "";
4031
- const group = groups.get(key) ?? {
4032
- project: projects.find((project) => project.id === key) ?? null,
4033
- total: 0,
4034
- done: 0
4035
- };
4036
- group.total += 1;
4037
- if (task.status === "COMPLETED" || task.status === "CANCELLED") group.done += 1;
4038
- groups.set(key, group);
4039
- }
4040
- return [...groups.values()].filter((group) => group.project !== null);
4041
- }, [tasks, projects]);
4079
+ },
4080
+ [filter]
4081
+ );
4042
4082
  const switchAccount = async (id) => {
4043
4083
  try {
4044
4084
  await api.activateAccount(id);
@@ -4047,10 +4087,10 @@ function TasksPanel() {
4047
4087
  setError(errorText7(caught));
4048
4088
  }
4049
4089
  };
4050
- const toggle = async (task) => {
4090
+ const move = async (task, status) => {
4091
+ if (task.status === status) return;
4051
4092
  try {
4052
- const next = task.status === "COMPLETED" ? "NEEDS-ACTION" : "COMPLETED";
4053
- const { task: updated } = await api.updateTask(task.id, { status: next });
4093
+ const { task: updated } = await api.updateTask(task.id, { status });
4054
4094
  setTasks((current) => current.map((item) => item.id === task.id ? updated : item));
4055
4095
  } catch (caught) {
4056
4096
  setError(errorText7(caught));
@@ -4060,6 +4100,7 @@ function TasksPanel() {
4060
4100
  try {
4061
4101
  await api.deleteTask(task.id);
4062
4102
  setTasks((current) => current.filter((item) => item.id !== task.id));
4103
+ notify("\u4EFB\u52A1\u5DF2\u5220\u9664");
4063
4104
  } catch (caught) {
4064
4105
  setError(errorText7(caught));
4065
4106
  }
@@ -4074,21 +4115,8 @@ function TasksPanel() {
4074
4115
  setError(errorText7(caught));
4075
4116
  }
4076
4117
  };
4077
- const sync = async () => {
4078
- if (accountId === null) return;
4079
- setBusy(true);
4080
- setError("");
4081
- try {
4082
- const result = await api.syncTasks(accountId);
4083
- notify(`\u5DF2\u540C\u6B65\uFF1A\u62C9\u53D6 ${String(result.result.pulled)}\uFF0C\u63A8\u9001 ${String(result.result.pushed)}`);
4084
- refresh();
4085
- } catch (caught) {
4086
- setError(errorText7(caught));
4087
- } finally {
4088
- setBusy(false);
4089
- }
4090
- };
4091
4118
  const save = async (draft) => {
4119
+ if (accountId === null) return;
4092
4120
  setBusy(true);
4093
4121
  setError("");
4094
4122
  try {
@@ -4115,7 +4143,8 @@ function TasksPanel() {
4115
4143
  });
4116
4144
  }
4117
4145
  setEditing(null);
4118
- setCreating(false);
4146
+ setCreating(null);
4147
+ notify("\u4EFB\u52A1\u5DF2\u4FDD\u5B58");
4119
4148
  refresh();
4120
4149
  } catch (caught) {
4121
4150
  setError(errorText7(caught));
@@ -4125,7 +4154,7 @@ function TasksPanel() {
4125
4154
  };
4126
4155
  return (0, import_react11.createElement)(
4127
4156
  "div",
4128
- { className: "mailharbor" },
4157
+ { className: "mailharbor mailharbor-tasks" },
4129
4158
  (0, import_react11.createElement)(
4130
4159
  "div",
4131
4160
  { className: "mailharbor-topbar" },
@@ -4143,72 +4172,21 @@ function TasksPanel() {
4143
4172
  )
4144
4173
  ),
4145
4174
  (0, import_react11.createElement)("span", { className: "spacer" }),
4146
- calendar === null ? null : (0, import_react11.createElement)(
4147
- "span",
4148
- { className: "small muted" },
4149
- calendar.lastSync === "" ? "\u672A\u540C\u6B65" : `\u540C\u6B65\u4E8E ${new Date(calendar.lastSync).toLocaleString()}`
4150
- ),
4151
- (0, import_react11.createElement)(
4152
- "button",
4153
- { className: "ghost", onClick: () => void sync(), disabled: busy },
4154
- busy ? "\u540C\u6B65\u4E2D\u2026" : "\u540C\u6B65 CalDAV"
4155
- ),
4156
4175
  (0, import_react11.createElement)(
4157
4176
  "button",
4158
- { className: "primary", onClick: () => setCreating(true) },
4177
+ { className: "primary", onClick: () => setCreating("NEEDS-ACTION") },
4159
4178
  (0, import_react11.createElement)(Icon, { name: "plus" }),
4160
4179
  "\u65B0\u5EFA\u4EFB\u52A1"
4161
4180
  )
4162
4181
  ),
4163
- toast === "" ? null : (0, import_react11.createElement)("div", { className: "small", style: { padding: "6px 14px", color: "var(--m-ok)" } }, toast),
4182
+ toast === "" ? null : (0, import_react11.createElement)("div", { className: "small", style: { padding: "6px 18px", color: "var(--m-ok)" } }, toast),
4164
4183
  error === "" ? null : (0, import_react11.createElement)(ErrorBar, { message: error, onDismiss: () => setError("") }),
4165
4184
  (0, import_react11.createElement)(
4166
4185
  "div",
4167
- { className: "tasks-body" },
4186
+ { className: "tasks-board-wrap" },
4168
4187
  (0, import_react11.createElement)(
4169
4188
  "div",
4170
- { className: "tasks-overview" },
4171
- (0, import_react11.createElement)(
4172
- "div",
4173
- { className: "tasks-progress" },
4174
- (0, import_react11.createElement)(
4175
- "div",
4176
- { className: "tasks-progress-head" },
4177
- (0, import_react11.createElement)("strong", null, "\u603B\u4F53\u8FDB\u5EA6"),
4178
- (0, import_react11.createElement)(
4179
- "span",
4180
- { className: "muted small" },
4181
- `${String(done)} / ${String(tasks.length)} \xB7 ${String(progress)}%`
4182
- )
4183
- ),
4184
- (0, import_react11.createElement)("div", { className: "tasks-bar" }, (0, import_react11.createElement)("span", { style: { width: `${String(progress)}%` } })),
4185
- (0, import_react11.createElement)("div", { className: "small muted" }, `\u5F85\u529E ${String(open.length)} \u6761`)
4186
- ),
4187
- ...byProject.map(
4188
- (group) => (0, import_react11.createElement)(
4189
- "div",
4190
- { key: group.project?.id ?? "none", className: "tasks-project" },
4191
- (0, import_react11.createElement)(
4192
- "div",
4193
- { className: "tasks-project-head" },
4194
- (0, import_react11.createElement)("span", { className: "name" }, group.project?.name ?? ""),
4195
- (0, import_react11.createElement)("span", { className: "muted small" }, `${String(group.done)}/${String(group.total)}`)
4196
- ),
4197
- (0, import_react11.createElement)(
4198
- "div",
4199
- { className: "tasks-bar small" },
4200
- (0, import_react11.createElement)("span", {
4201
- style: {
4202
- width: `${String(group.total === 0 ? 0 : Math.round(group.done / group.total * 100))}%`
4203
- }
4204
- })
4205
- )
4206
- )
4207
- )
4208
- ),
4209
- (0, import_react11.createElement)(
4210
- "div",
4211
- { className: "tasks-list-wrap" },
4189
+ { className: "tasks-toolbar" },
4212
4190
  (0, import_react11.createElement)(
4213
4191
  "div",
4214
4192
  { className: "tasks-filters" },
@@ -4224,73 +4202,142 @@ function TasksPanel() {
4224
4202
  )
4225
4203
  )
4226
4204
  ),
4227
- visible.length === 0 ? (0, import_react11.createElement)(
4228
- "div",
4229
- { className: "mailharbor-empty" },
4230
- (0, import_react11.createElement)(Icon, { name: "checklist", size: 28 }),
4231
- (0, import_react11.createElement)("div", { className: "title" }, "\u6CA1\u6709\u4EFB\u52A1"),
4232
- (0, import_react11.createElement)("div", { className: "small" }, "\u53EF\u4EE5\u5728\u8FD9\u91CC\u65B0\u5EFA\uFF0C\u6216\u5728\u90AE\u4EF6\u91CC\u7528\u300C\u63D0\u53D6\u4EFB\u52A1\u300D\u3002")
4233
- ) : (0, import_react11.createElement)(
4234
- "div",
4235
- { className: "tasks-list" },
4236
- visible.map(
4237
- (task) => (0, import_react11.createElement)(
4205
+ (0, import_react11.createElement)("span", { className: "spacer" }),
4206
+ (0, import_react11.createElement)(
4207
+ "select",
4208
+ {
4209
+ value: projectFilter,
4210
+ onChange: (event) => setProjectFilter(event.target.value),
4211
+ "aria-label": "\u6309\u9879\u76EE\u7B5B\u9009"
4212
+ },
4213
+ (0, import_react11.createElement)("option", { value: "" }, "\u5168\u90E8\u9879\u76EE"),
4214
+ projects.map((project) => (0, import_react11.createElement)("option", { key: project.id, value: project.id }, project.name))
4215
+ ),
4216
+ (0, import_react11.createElement)("span", { className: "small muted" }, "\u62D6\u52A8\u5361\u7247\u53EF\u6539\u53D8\u72B6\u6001")
4217
+ ),
4218
+ (0, import_react11.createElement)(
4219
+ "div",
4220
+ { className: "tasks-board" },
4221
+ COLUMNS.map((column) => {
4222
+ const cards = tasks.filter((task) => task.status === column.status).filter(matchesFilter).filter((task) => projectFilter === "" || task.projectId === projectFilter).sort((left, right) => {
4223
+ const leftDue = left.due === "" ? "9999" : left.due.slice(0, 10);
4224
+ const rightDue = right.due === "" ? "9999" : right.due.slice(0, 10);
4225
+ return leftDue.localeCompare(rightDue);
4226
+ });
4227
+ return (0, import_react11.createElement)(
4228
+ "div",
4229
+ {
4230
+ key: column.status,
4231
+ className: `task-column task-column-${column.status}`,
4232
+ onDragOver: (event) => {
4233
+ event.preventDefault();
4234
+ },
4235
+ onDrop: (event) => {
4236
+ event.preventDefault();
4237
+ const task = tasks.find((item) => item.id === dragging);
4238
+ if (task !== void 0) void move(task, column.status);
4239
+ setDragging(null);
4240
+ }
4241
+ },
4242
+ (0, import_react11.createElement)(
4238
4243
  "div",
4239
- { key: task.id, className: `tasks-row${task.status === "COMPLETED" ? " done" : ""}` },
4240
- (0, import_react11.createElement)("input", {
4241
- type: "checkbox",
4242
- checked: task.status === "COMPLETED",
4243
- "aria-label": "\u5B8C\u6210\u4EFB\u52A1",
4244
- onChange: () => void toggle(task)
4245
- }),
4244
+ { className: "task-column-head" },
4245
+ (0, import_react11.createElement)("span", { className: `task-dot ${column.status}` }),
4246
+ (0, import_react11.createElement)("strong", null, column.label),
4247
+ (0, import_react11.createElement)("span", { className: "task-column-count" }, String(cards.length)),
4248
+ (0, import_react11.createElement)("span", { className: "spacer" }),
4246
4249
  (0, import_react11.createElement)(
4247
4250
  "button",
4248
- { className: "tasks-title", onClick: () => setEditing(task) },
4249
- (0, import_react11.createElement)("span", null, task.title),
4250
- (0, import_react11.createElement)(
4251
- "span",
4252
- { className: "tasks-meta" },
4253
- task.due === "" ? null : (0, import_react11.createElement)(
4254
- "span",
4251
+ {
4252
+ className: "subtle icon",
4253
+ title: `\u5728\u300C${column.label}\u300D\u4E2D\u65B0\u5EFA`,
4254
+ onClick: () => setCreating(column.status)
4255
+ },
4256
+ (0, import_react11.createElement)(Icon, { name: "plus", size: 14 })
4257
+ )
4258
+ ),
4259
+ (0, import_react11.createElement)(
4260
+ "div",
4261
+ { className: "task-column-body" },
4262
+ cards.length === 0 ? (0, import_react11.createElement)("div", { className: "task-column-empty" }, column.hint) : cards.map(
4263
+ (task) => (0, import_react11.createElement)(
4264
+ "article",
4265
+ {
4266
+ key: task.id,
4267
+ className: `task-card${dragging === task.id ? " dragging" : ""}`,
4268
+ draggable: true,
4269
+ onDragStart: () => setDragging(task.id),
4270
+ onDragEnd: () => setDragging(null),
4271
+ onClick: () => setEditing(task)
4272
+ },
4273
+ (0, import_react11.createElement)(
4274
+ "div",
4275
+ { className: "task-card-top" },
4276
+ (0, import_react11.createElement)("span", { className: `task-priority p${String(task.priority)}` }),
4277
+ (0, import_react11.createElement)("span", { className: "task-card-title" }, task.title),
4278
+ (0, import_react11.createElement)(
4279
+ "button",
4280
+ {
4281
+ className: "task-card-delete",
4282
+ title: "\u5220\u9664\u4EFB\u52A1",
4283
+ onClick: (event) => {
4284
+ event.stopPropagation();
4285
+ void remove(task);
4286
+ }
4287
+ },
4288
+ (0, import_react11.createElement)(Icon, { name: "close", size: 12 })
4289
+ )
4290
+ ),
4291
+ task.notes === "" ? null : (0, import_react11.createElement)("p", { className: "task-card-notes" }, task.notes),
4292
+ (0, import_react11.createElement)(
4293
+ "div",
4294
+ { className: "task-card-meta" },
4295
+ task.due === "" ? null : (0, import_react11.createElement)(
4296
+ "span",
4297
+ {
4298
+ className: task.due.slice(0, 10) < dateKey(/* @__PURE__ */ new Date()) && task.status !== "COMPLETED" ? "task-due overdue" : "task-due"
4299
+ },
4300
+ (0, import_react11.createElement)(Icon, { name: "calendar", size: 12 }),
4301
+ dueText(task.due)
4302
+ ),
4303
+ task.owner === "" ? null : (0, import_react11.createElement)("span", { className: "task-owner" }, task.owner),
4304
+ task.projectId === null ? null : (0, import_react11.createElement)(
4305
+ "span",
4306
+ { className: "task-project" },
4307
+ projects.find((project) => project.id === task.projectId)?.name ?? "\u9879\u76EE"
4308
+ ),
4309
+ task.priority > 0 ? (0, import_react11.createElement)(
4310
+ "span",
4311
+ { className: "task-priority-label" },
4312
+ `\u4F18\u5148\u7EA7 ${PRIORITY_LABEL[task.priority] ?? String(task.priority)}`
4313
+ ) : null
4314
+ ),
4315
+ task.messageKey === "" ? null : (0, import_react11.createElement)(
4316
+ "button",
4255
4317
  {
4256
- className: task.due.slice(0, 10) < dateKey(/* @__PURE__ */ new Date()) ? "due overdue" : "due"
4318
+ className: "task-source",
4319
+ onClick: (event) => {
4320
+ event.stopPropagation();
4321
+ void openSource(task);
4322
+ }
4257
4323
  },
4258
- dueText(task.due)
4259
- ),
4260
- task.owner === "" ? null : (0, import_react11.createElement)("span", null, task.owner),
4261
- (0, import_react11.createElement)("span", { className: "badge" }, STATUS_LABEL[task.status] ?? task.status),
4262
- task.projectId === null ? null : (0, import_react11.createElement)(
4263
- "span",
4264
- { className: "badge" },
4265
- projects.find((project) => project.id === task.projectId)?.name ?? "\u9879\u76EE"
4324
+ (0, import_react11.createElement)(Icon, { name: "mail", size: 12 }),
4325
+ "\u6765\u6E90\u90AE\u4EF6"
4266
4326
  )
4267
4327
  )
4268
- ),
4269
- task.messageKey === "" ? null : (0, import_react11.createElement)(
4270
- "button",
4271
- {
4272
- className: "subtle small",
4273
- title: "\u6253\u5F00\u6765\u6E90\u90AE\u4EF6",
4274
- onClick: () => void openSource(task)
4275
- },
4276
- "\u6765\u6E90"
4277
- ),
4278
- (0, import_react11.createElement)(
4279
- "button",
4280
- { className: "subtle icon", title: "\u5220\u9664", onClick: () => void remove(task) },
4281
- (0, import_react11.createElement)(Icon, { name: "trash", size: 13 })
4282
4328
  )
4283
4329
  )
4284
- )
4285
- )
4330
+ );
4331
+ })
4286
4332
  )
4287
4333
  ),
4288
- creating || editing !== null ? (0, import_react11.createElement)(TaskEditor, {
4334
+ creating !== null || editing !== null ? (0, import_react11.createElement)(TaskEditor, {
4289
4335
  task: editing,
4336
+ status: creating ?? "NEEDS-ACTION",
4290
4337
  projects,
4291
4338
  busy,
4292
4339
  onClose: () => {
4293
- setCreating(false);
4340
+ setCreating(null);
4294
4341
  setEditing(null);
4295
4342
  },
4296
4343
  onSave: (draft) => void save(draft)
@@ -4299,13 +4346,15 @@ function TasksPanel() {
4299
4346
  }
4300
4347
  function TaskEditor({
4301
4348
  task,
4349
+ status,
4302
4350
  projects,
4303
4351
  busy,
4304
4352
  onClose,
4305
4353
  onSave
4306
4354
  }) {
4307
4355
  const [draft, setDraft] = (0, import_react11.useState)({
4308
- ...EMPTY_TASK,
4356
+ ...EMPTY,
4357
+ status,
4309
4358
  ...task === null ? {} : {
4310
4359
  id: task.id,
4311
4360
  title: task.title,
@@ -4323,111 +4372,139 @@ function TaskEditor({
4323
4372
  {
4324
4373
  title: task === null ? "\u65B0\u5EFA\u4EFB\u52A1" : "\u7F16\u8F91\u4EFB\u52A1",
4325
4374
  onClose,
4375
+ wide: true,
4326
4376
  footer: (0, import_react11.createElement)(
4327
4377
  "div",
4328
- { style: { display: "flex", gap: "8px", width: "100%", justifyContent: "flex-end" } },
4378
+ { className: "task-editor-foot" },
4379
+ (0, import_react11.createElement)("span", { className: "small muted" }, "\u63D0\u793A\uFF1A\u4EFB\u52A1\u4F1A\u4FDD\u5B58\u5728\u672C\u5730\uFF0C\u53EF\u5728\u770B\u677F\u4E2D\u62D6\u52A8\u6539\u53D8\u72B6\u6001\u3002"),
4380
+ (0, import_react11.createElement)("span", { className: "spacer" }),
4329
4381
  (0, import_react11.createElement)("button", { className: "ghost", onClick: onClose }, "\u53D6\u6D88"),
4330
4382
  (0, import_react11.createElement)(
4331
4383
  "button",
4332
- { className: "primary", disabled: busy || draft.title.trim() === "", onClick: () => onSave(draft) },
4333
- busy ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58"
4334
- )
4335
- )
4336
- },
4337
- (0, import_react11.createElement)(
4338
- "div",
4339
- { className: "mailharbor-field" },
4340
- (0, import_react11.createElement)("label", null, "\u6807\u9898"),
4341
- (0, import_react11.createElement)("input", {
4342
- value: draft.title,
4343
- autoFocus: true,
4344
- onChange: (event) => set({ title: event.target.value })
4345
- })
4346
- ),
4347
- (0, import_react11.createElement)(
4348
- "div",
4349
- { className: "mailharbor-field" },
4350
- (0, import_react11.createElement)("label", null, "\u5907\u6CE8"),
4351
- (0, import_react11.createElement)("textarea", {
4352
- rows: 3,
4353
- value: draft.notes,
4354
- onChange: (event) => set({ notes: event.target.value })
4355
- })
4356
- ),
4357
- (0, import_react11.createElement)(
4358
- "div",
4359
- { className: "mailharbor-grid" },
4360
- (0, import_react11.createElement)(
4361
- "div",
4362
- { className: "mailharbor-field" },
4363
- (0, import_react11.createElement)("label", null, "\u622A\u6B62\u65E5\u671F"),
4364
- (0, import_react11.createElement)("input", {
4365
- type: "date",
4366
- value: draft.due,
4367
- onChange: (event) => set({ due: event.target.value })
4368
- })
4369
- ),
4370
- (0, import_react11.createElement)(
4371
- "div",
4372
- { className: "mailharbor-field" },
4373
- (0, import_react11.createElement)("label", null, "\u4F18\u5148\u7EA7"),
4374
- (0, import_react11.createElement)(
4375
- "select",
4376
4384
  {
4377
- value: String(draft.priority),
4378
- onChange: (event) => set({ priority: Number(event.target.value) })
4385
+ className: "primary",
4386
+ disabled: busy || draft.title.trim() === "",
4387
+ onClick: () => onSave(draft)
4379
4388
  },
4380
- (0, import_react11.createElement)("option", { value: "0" }, "\u65E0"),
4381
- (0, import_react11.createElement)("option", { value: "5" }, "\u4E2D"),
4382
- (0, import_react11.createElement)("option", { value: "9" }, "\u9AD8")
4389
+ busy ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58\u4EFB\u52A1"
4383
4390
  )
4384
4391
  )
4385
- ),
4392
+ },
4386
4393
  (0, import_react11.createElement)(
4387
4394
  "div",
4388
- { className: "mailharbor-grid" },
4395
+ { className: "task-editor" },
4389
4396
  (0, import_react11.createElement)(
4390
4397
  "div",
4391
- { className: "mailharbor-field" },
4392
- (0, import_react11.createElement)("label", null, "\u72B6\u6001"),
4398
+ { className: "task-editor-main" },
4393
4399
  (0, import_react11.createElement)(
4394
- "select",
4395
- {
4396
- value: draft.status,
4397
- onChange: (event) => set({ status: event.target.value })
4398
- },
4399
- Object.entries(STATUS_LABEL).map(([value, label]) => (0, import_react11.createElement)("option", { key: value, value }, label))
4400
+ "div",
4401
+ { className: "task-editor-title" },
4402
+ (0, import_react11.createElement)("input", {
4403
+ value: draft.title,
4404
+ autoFocus: true,
4405
+ placeholder: "\u8981\u505A\u4EC0\u4E48\uFF1F",
4406
+ onChange: (event) => set({ title: event.target.value })
4407
+ })
4408
+ ),
4409
+ (0, import_react11.createElement)(
4410
+ "div",
4411
+ { className: "task-editor-section" },
4412
+ (0, import_react11.createElement)("label", null, "\u5907\u6CE8"),
4413
+ (0, import_react11.createElement)("textarea", {
4414
+ rows: 5,
4415
+ value: draft.notes,
4416
+ placeholder: "\u80CC\u666F\u3001\u4E0B\u4E00\u6B65\u3001\u94FE\u63A5\u2026",
4417
+ onChange: (event) => set({ notes: event.target.value })
4418
+ })
4419
+ ),
4420
+ (0, import_react11.createElement)(
4421
+ "div",
4422
+ { className: "task-editor-section" },
4423
+ (0, import_react11.createElement)("label", null, "\u6765\u6E90"),
4424
+ draft.id === void 0 ? (0, import_react11.createElement)("span", { className: "small muted" }, "\u4ECE\u90AE\u4EF6\u63D0\u53D6\u7684\u4EFB\u52A1\u4F1A\u81EA\u52A8\u5173\u8054\u6765\u6E90\u90AE\u4EF6\u3002") : (0, import_react11.createElement)(
4425
+ "span",
4426
+ { className: "small muted" },
4427
+ task?.messageSubject !== void 0 && task.messageSubject !== "" ? `\u6765\u81EA\u90AE\u4EF6\uFF1A${task.messageSubject}` : "\u65E0\u5173\u8054\u90AE\u4EF6"
4428
+ )
4400
4429
  )
4401
4430
  ),
4402
4431
  (0, import_react11.createElement)(
4403
- "div",
4404
- { className: "mailharbor-field" },
4405
- (0, import_react11.createElement)("label", null, "\u9879\u76EE"),
4432
+ "aside",
4433
+ { className: "task-editor-side" },
4406
4434
  (0, import_react11.createElement)(
4407
- "select",
4408
- {
4409
- value: draft.projectId,
4410
- onChange: (event) => set({ projectId: event.target.value })
4411
- },
4412
- (0, import_react11.createElement)("option", { value: "" }, "\u65E0\u9879\u76EE"),
4413
- projects.map((project) => (0, import_react11.createElement)("option", { key: project.id, value: project.id }, project.name))
4435
+ "div",
4436
+ { className: "task-editor-field" },
4437
+ (0, import_react11.createElement)("label", null, "\u72B6\u6001"),
4438
+ (0, import_react11.createElement)(
4439
+ "select",
4440
+ {
4441
+ value: draft.status,
4442
+ onChange: (event) => set({ status: event.target.value })
4443
+ },
4444
+ COLUMNS.map((column) => (0, import_react11.createElement)("option", { key: column.status, value: column.status }, column.label))
4445
+ )
4446
+ ),
4447
+ (0, import_react11.createElement)(
4448
+ "div",
4449
+ { className: "task-editor-field" },
4450
+ (0, import_react11.createElement)("label", null, "\u622A\u6B62\u65E5\u671F"),
4451
+ (0, import_react11.createElement)("input", {
4452
+ type: "date",
4453
+ value: draft.due,
4454
+ onChange: (event) => set({ due: event.target.value })
4455
+ })
4456
+ ),
4457
+ (0, import_react11.createElement)(
4458
+ "div",
4459
+ { className: "task-editor-field" },
4460
+ (0, import_react11.createElement)("label", null, "\u4F18\u5148\u7EA7"),
4461
+ (0, import_react11.createElement)(
4462
+ "div",
4463
+ { className: "task-priority-picker" },
4464
+ [0, 5, 9].map(
4465
+ (value) => (0, import_react11.createElement)(
4466
+ "button",
4467
+ {
4468
+ key: value,
4469
+ className: draft.priority === value ? "active" : "",
4470
+ onClick: () => set({ priority: value })
4471
+ },
4472
+ PRIORITY_LABEL[value] ?? String(value)
4473
+ )
4474
+ )
4475
+ )
4476
+ ),
4477
+ (0, import_react11.createElement)(
4478
+ "div",
4479
+ { className: "task-editor-field" },
4480
+ (0, import_react11.createElement)("label", null, "\u9879\u76EE"),
4481
+ (0, import_react11.createElement)(
4482
+ "select",
4483
+ {
4484
+ value: draft.projectId,
4485
+ onChange: (event) => set({ projectId: event.target.value })
4486
+ },
4487
+ (0, import_react11.createElement)("option", { value: "" }, "\u65E0\u9879\u76EE"),
4488
+ projects.map((project) => (0, import_react11.createElement)("option", { key: project.id, value: project.id }, project.name))
4489
+ )
4490
+ ),
4491
+ (0, import_react11.createElement)(
4492
+ "div",
4493
+ { className: "task-editor-field" },
4494
+ (0, import_react11.createElement)("label", null, "\u8D1F\u8D23\u4EBA"),
4495
+ (0, import_react11.createElement)("input", {
4496
+ value: draft.owner,
4497
+ placeholder: "\u53EF\u9009",
4498
+ onChange: (event) => set({ owner: event.target.value })
4499
+ })
4414
4500
  )
4415
4501
  )
4416
- ),
4417
- (0, import_react11.createElement)(
4418
- "div",
4419
- { className: "mailharbor-field" },
4420
- (0, import_react11.createElement)("label", null, "\u8D1F\u8D23\u4EBA"),
4421
- (0, import_react11.createElement)("input", {
4422
- value: draft.owner,
4423
- onChange: (event) => set({ owner: event.target.value })
4424
- })
4425
4502
  )
4426
4503
  );
4427
4504
  }
4428
4505
 
4429
4506
  // src/client/index.tsx
4430
- var VERSION = "0.5.1";
4507
+ var VERSION = "0.7.0";
4431
4508
  var inject = ["slots"];
4432
4509
  var PANEL_ID = "mailharbor";
4433
4510
  var TASKS_PANEL_ID = "mailharbor-tasks";