/**
 * folk-inbox-client — Collaborative email client.
 *
 * Shows mailbox list, thread inbox, thread detail with comments,
 * reply/forward compose, approval workflow, personal inboxes,
 * and agent inbox management. Includes a help/guide popout.
 */

import { mailboxSchema, type MailboxDoc } from "../schemas";
import type { DocumentId } from "../../../shared/local-first/document";
import { TourEngine } from "../../../shared/tour-engine";
import { ViewHistory } from "../../../shared/view-history.js";
import { getAccessToken, getUsername } from "../../../lib/rspace-header";
import { startPresenceHeartbeat } from '../../../shared/collab-presence';
import { getModuleApiBase } from "../../../shared/url-helpers";
import { Editor } from '@tiptap/core';
import StarterKit from '@tiptap/starter-kit';
import Link from '@tiptap/extension-link';
import TaskList from '@tiptap/extension-task-list';
import TaskItem from '@tiptap/extension-task-item';
import Placeholder from '@tiptap/extension-placeholder';
import Underline from '@tiptap/extension-underline';
import { Markdown } from 'tiptap-markdown';
import * as Y from 'yjs';
import { IndexeddbPersistence } from 'y-indexeddb';
import { ySyncPlugin, yUndoPlugin, yCursorPlugin } from '@tiptap/y-tiptap';
import { RSpaceYjsProvider } from '../../../shared/yjs-ws-provider';
import { tiptapToMarkdown, extractPlainTextFromTiptap } from '../../../shared/markdown-tiptap';
import { marked } from 'marked';

type ComposeMode = 'new' | 'reply' | 'reply-all' | 'forward';

class FolkInboxClient extends HTMLElement {
	private shadow: ShadowRoot;
	private space = "demo";
	private _offlineUnsubs: (() => void)[] = [];
	private _stopPresence: (() => void) | null = null;
	private view: "mailboxes" | "threads" | "thread" | "approvals" | "personal" | "agents" = "mailboxes";
	private mailboxes: any[] = [];
	private threads: any[] = [];
	private currentMailbox: any = null;
	private currentThread: any = null;
	private approvals: any[] = [];
	private personalInboxes: any[] = [];
	private agentInboxes: any[] = [];
	private filter: "all" | "open" | "snoozed" | "closed" = "all";
	private helpOpen = false;
	private composeOpen = false;
	private composeMode: ComposeMode = 'new';
	private showingSampleData = false;
	private connectFormOpen = false;
	private agentFormOpen = false;
	private _usernameCache = new Map<string, string>();
	private _currentUsername: string | null = null;
	private _history = new ViewHistory<"mailboxes" | "threads" | "thread" | "approvals" | "personal" | "agents">("mailboxes", "rinbox");
	private _fwdStatus: 'loading' | 'unavailable' | 'no-email' | 'ready' | 'enabled' | 'error' = 'loading';
	private _fwdAddress = '';
	private _fwdTarget = '';
	private _fwdModalOpen = false;
	private _fwdBusy = false;
	private _fwdError = '';
	// TipTap / Yjs editor instances
	private composeEditor: Editor | null = null;
	private composeYdoc: Y.Doc | null = null;
	private composeYjsProvider: RSpaceYjsProvider | null = null;
	private composeYIndexedDb: IndexeddbPersistence | null = null;
	private commentEditor: Editor | null = null;
	private demoApprovals: any[] = [
		{
			id: "a1", subject: "Re: Q1 Budget approval for rSpace infrastructure", status: "PENDING",
			to_addresses: ["finance@example.org"], required_signatures: 3, signature_count: 1,
			body_text: "Approved. Please proceed with the cloud infrastructure upgrade as outlined. Budget: $4,200/quarter.",
			created_at: new Date(Date.now() - 4 * 3600000).toISOString(), reply_type: 'reply',
			signers: [
				{ name: "Alice Chen", vote: "APPROVE", signed_at: new Date(Date.now() - 3 * 3600000).toISOString() },
				{ name: "Bob Martinez", vote: null, signed_at: null },
				{ name: "Carol Wu", vote: null, signed_at: null },
			],
		},
		{
			id: "a2", subject: "Partnership agreement with Acme Corp", status: "PENDING",
			to_addresses: ["legal@acmecorp.com", "partnerships@example.org"], required_signatures: 2, signature_count: 1,
			body_text: "We've reviewed the terms and are ready to proceed. Please find the signed MOU attached.",
			created_at: new Date(Date.now() - 12 * 3600000).toISOString(), reply_type: 'new',
			signers: [
				{ name: "Dave Park", vote: "APPROVE", signed_at: new Date(Date.now() - 10 * 3600000).toISOString() },
				{ name: "Alice Chen", vote: null, signed_at: null },
			],
		},
		{
			id: "a3", subject: "Re: Security incident disclosure", status: "APPROVED",
			to_addresses: ["security@example.com"], required_signatures: 2, signature_count: 2,
			body_text: "Disclosure notice prepared and co-signed. Sending to affected parties.",
			created_at: new Date(Date.now() - 48 * 3600000).toISOString(), reply_type: 'reply',
			signers: [
				{ name: "Bob Martinez", vote: "APPROVE", signed_at: new Date(Date.now() - 46 * 3600000).toISOString() },
				{ name: "Carol Wu", vote: "APPROVE", signed_at: new Date(Date.now() - 44 * 3600000).toISOString() },
			],
		},
	];
	private demoThreads: Record<string, any[]> = {
		space: [
			{ id: "s1", from_name: "Alice Chen", from_address: "alice@example.com", subject: "Welcome to the space mailbox", status: "open", is_read: true, is_starred: true, replied: false, forwarded: false, needsAttention: false, comment_count: 1, received_at: new Date(Date.now() - 1 * 3600000).toISOString(), body_text: "This is the shared space mailbox. All space members can read and triage messages here.", direction: 'inbound', comments: [{ username: "Bob Martinez", body: "Great, all set up!", created_at: new Date(Date.now() - 0.5 * 3600000).toISOString() }] },
			{ id: "s2", from_name: "External Contact", from_address: "contact@example.org", subject: "Partnership inquiry", status: "open", is_read: false, is_starred: false, replied: false, forwarded: false, needsAttention: true, comment_count: 0, received_at: new Date(Date.now() - 3 * 3600000).toISOString(), body_text: "We'd love to discuss a potential collaboration with your community. Could we schedule a call?", direction: 'inbound', comments: [] },
			{ id: "s3", from_name: "Community Bot", from_address: "bot@rspace.online", subject: "Weekly digest: 12 new members, 3 proposals", status: "open", is_read: true, is_starred: false, replied: false, forwarded: false, needsAttention: false, comment_count: 0, received_at: new Date(Date.now() - 12 * 3600000).toISOString(), body_text: "Here's your weekly summary:\n\n- 12 new members joined\n- 3 governance proposals submitted\n- 2 events scheduled\n\nVisit the dashboard for details.", direction: 'inbound', comments: [] },
			{ id: "s4", from_name: "Dave Park", from_address: "dave@example.com", subject: "Governance proposal feedback", status: "closed", is_read: true, is_starred: false, replied: true, forwarded: false, needsAttention: false, comment_count: 2, received_at: new Date(Date.now() - 48 * 3600000).toISOString(), body_text: "I've reviewed the treasury allocation proposal. My feedback is inline below.", direction: 'inbound', comments: [{ username: "Alice Chen", body: "Good points, incorporated into v2.", created_at: new Date(Date.now() - 46 * 3600000).toISOString() }, { username: "Dave Park", body: "Looks great now, thanks!", created_at: new Date(Date.now() - 44 * 3600000).toISOString() }] },
		],
		team: [
			{ id: "t1", from_name: "Alice Chen", from_address: "alice@example.com", subject: "Sprint planning notes", status: "open", is_read: true, is_starred: true, replied: true, forwarded: false, needsAttention: false, comment_count: 3, received_at: new Date(Date.now() - 2 * 3600000).toISOString(), body_text: "Here are the sprint planning notes from today's session. We agreed on the following priorities for the next two weeks:\n\n1. Ship local-first sync for notes module\n2. Polish the calendar demo mode\n3. Review provider registry API\n\nLet me know if I missed anything.", direction: 'inbound', comments: [{ username: "Bob Martinez", body: "Looks good! I'd add the inbox overhaul too.", created_at: new Date(Date.now() - 1.5 * 3600000).toISOString() }, { username: "Carol Wu", body: "Agreed, calendar polish is top priority.", created_at: new Date(Date.now() - 1 * 3600000).toISOString() }, { username: "Alice Chen", body: "Updated the list. Thanks!", created_at: new Date(Date.now() - 0.5 * 3600000).toISOString() }] },
			{ id: "t2", from_name: "Bob Martinez", from_address: "bob@example.com", subject: "Deploy checklist for v2.1", status: "open", is_read: false, is_starred: false, replied: false, forwarded: false, needsAttention: true, comment_count: 1, received_at: new Date(Date.now() - 5 * 3600000).toISOString(), body_text: "Here is the deploy checklist for v2.1. Please review before we cut the release.\n\n- [ ] Run full test suite\n- [ ] Update changelog\n- [ ] Tag release in Gitea\n- [ ] Deploy to staging\n- [ ] Smoke test all modules", direction: 'inbound', comments: [{ username: "Alice Chen", body: "I can handle the changelog update.", created_at: new Date(Date.now() - 4 * 3600000).toISOString() }] },
			{ id: "t3", from_name: "Carol Wu", from_address: "carol@example.com", subject: "Design system color tokens", status: "snoozed", is_read: true, is_starred: false, replied: false, forwarded: false, needsAttention: false, comment_count: 0, received_at: new Date(Date.now() - 24 * 3600000).toISOString(), body_text: "I've been working on standardizing our color tokens across all modules. The current approach of inline hex values is getting unwieldy. Proposal attached.", direction: 'inbound', comments: [] },
			{ id: "t4", from_name: "Dave Park", from_address: "dave@example.com", subject: "Q1 Retrospective summary", status: "closed", is_read: true, is_starred: false, replied: true, forwarded: false, needsAttention: false, comment_count: 5, received_at: new Date(Date.now() - 72 * 3600000).toISOString(), body_text: "Summary of our Q1 retrospective:\n\nWhat went well: Local-first architecture, community engagement, rapid prototyping.\nWhat to improve: Documentation, test coverage, onboarding flow.", direction: 'inbound', comments: [{ username: "Alice Chen", body: "Great summary, Dave.", created_at: new Date(Date.now() - 70 * 3600000).toISOString() }, { username: "Bob Martinez", body: "+1 on improving docs.", created_at: new Date(Date.now() - 69 * 3600000).toISOString() }, { username: "Carol Wu", body: "I can lead the onboarding redesign.", created_at: new Date(Date.now() - 68 * 3600000).toISOString() }, { username: "Dave Park", body: "Sounds good, let's schedule a kickoff.", created_at: new Date(Date.now() - 67 * 3600000).toISOString() }, { username: "Alice Chen", body: "Added to next sprint.", created_at: new Date(Date.now() - 66 * 3600000).toISOString() }] },
		],
		support: [
			{ id: "t5", from_name: "New User", from_address: "newuser@example.com", subject: "How do I create a space?", status: "open", is_read: false, is_starred: false, replied: false, forwarded: false, needsAttention: true, comment_count: 0, received_at: new Date(Date.now() - 1 * 3600000).toISOString(), body_text: "Hi, I just signed up and I'm not sure how to create my own space. The docs mention a space switcher but I can't find it. Could you point me in the right direction?", direction: 'inbound', comments: [] },
			{ id: "t6", from_name: "Partner Org", from_address: "partner@example.org", subject: "Integration API access request", status: "open", is_read: true, is_starred: true, replied: true, forwarded: false, needsAttention: false, comment_count: 2, received_at: new Date(Date.now() - 8 * 3600000).toISOString(), body_text: "We'd like to integrate our platform with rSpace modules via the API. Could you provide API documentation and access credentials for our staging environment?", direction: 'inbound', comments: [{ username: "Team Bot", body: "Request logged. Assigning to API team.", created_at: new Date(Date.now() - 7 * 3600000).toISOString() }, { username: "Bob Martinez", body: "I'll send over the API docs today.", created_at: new Date(Date.now() - 6 * 3600000).toISOString() }] },
			{ id: "t7", from_name: "Community Member", from_address: "member@example.com", subject: "Feature request: dark mode", status: "closed", is_read: true, is_starred: false, replied: false, forwarded: true, needsAttention: false, comment_count: 4, received_at: new Date(Date.now() - 96 * 3600000).toISOString(), body_text: "Would love to see a proper dark mode toggle. The current theme is close but some panels still have bright backgrounds.", direction: 'inbound', comments: [{ username: "Carol Wu", body: "This is on our roadmap! Targeting next release.", created_at: new Date(Date.now() - 90 * 3600000).toISOString() }, { username: "Community Member", body: "Awesome, looking forward to it.", created_at: new Date(Date.now() - 88 * 3600000).toISOString() }, { username: "Carol Wu", body: "Dark mode shipped in v2.0!", created_at: new Date(Date.now() - 48 * 3600000).toISOString() }, { username: "Community Member", body: "Looks great, thanks!", created_at: new Date(Date.now() - 46 * 3600000).toISOString() }] },
		],
	};

	// Guided tour
	private _tour!: TourEngine;
	private static readonly TOUR_STEPS = [
		{ target: '[data-nav="mailboxes"]', title: "Mailboxes", message: "Shared team mailboxes — everyone sees the same inbox, threads, and comments.", advanceOnClick: true },
		{ target: '[data-nav="approvals"]', title: "Approvals", message: "Outbound emails require co-signer approval before sending. Review and sign here.", advanceOnClick: true },
		{ target: '[data-action="help"]', title: "Info", message: "Open the guide anytime to learn how collaborative email works in rInbox.", advanceOnClick: true },
	];

	constructor() {
		super();
		this.shadow = this.attachShadow({ mode: "open" });
		this._tour = new TourEngine(
			this.shadow,
			FolkInboxClient.TOUR_STEPS,
			"rinbox_tour_done",
			() => this.shadow.host as HTMLElement,
		);
	}

	connectedCallback() {
		this.space = this.getAttribute("space") || "demo";
		this._loadUsername();
		this._checkForwardStatus();
		if (this.space === "demo") { this.loadDemoData(); }
		else { this.subscribeOffline(); this.loadMailboxes(); }
		this._stopPresence = startPresenceHeartbeat(() => ({ module: 'rinbox', context: this.currentThread?.subject || 'Inbox' }));
		window.addEventListener('rspace-view-restored', this._onViewRestored as EventListener);
	}

	disconnectedCallback() {
		this.destroyEditors();
		for (const unsub of this._offlineUnsubs) unsub();
		this._offlineUnsubs = [];
		this._stopPresence?.();
		this._history.destroy();
		window.removeEventListener('rspace-view-restored', this._onViewRestored as EventListener);
	}

	private _onViewRestored = (e: CustomEvent) => {
		if (e.detail?.moduleId !== 'rinbox') return;
		if (this.view === "thread") this.composeOpen = false;
		if (e.detail.view === "mailboxes") this.currentMailbox = null;
		this.view = e.detail.view;
		this.render();
	};

	/** Extract username from EncryptID session */
	private _loadUsername() {
		this._currentUsername = getUsername();
	}

	/** Resolve a DID to a display name */
	private displayName(did: string | null | undefined): string {
		if (!did) return 'Anonymous';
		if (did.startsWith('agent:')) return `Bot (${did.slice(6, 14)}...)`;
		if (this._usernameCache.has(did)) return this._usernameCache.get(did)!;
		// Truncate DID for display
		return did.length > 20 ? did.slice(0, 8) + '...' + did.slice(-6) : did;
	}

	private async subscribeOffline() {
		const runtime = (window as any).__rspaceOfflineRuntime;
		if (!runtime?.isInitialized) return;

		try {
			const docs = await runtime.subscribeModule('inbox', 'mailboxes', mailboxSchema);
			if (docs.size > 0 && this.mailboxes.length === 0) {
				const fromDocs: any[] = [];
				for (const [docId, doc] of docs) {
					const d = doc as MailboxDoc;
					if (!d?.mailbox) continue;
					fromDocs.push({
						slug: d.mailbox.slug, name: d.mailbox.name,
						email: d.mailbox.email, description: d.mailbox.description,
						thread_count: Object.keys(d.threads || {}).length,
					});
					this._offlineUnsubs.push(runtime.onChange(docId, () => {}));
				}
				if (fromDocs.length > 0) {
					this.mailboxes = fromDocs;
					this.render();
				}
			}
		} catch { /* runtime unavailable */ }
	}

	private loadDemoData() {
		this.mailboxes = [
			{ slug: "space", name: `${this.space}@rspace.online`, email: `${this.space}@rspace.online`, description: "Shared space mailbox — all members can read and triage", thread_count: 4, unread_count: 1, primary: true },
			{ slug: "team", name: "Team Inbox", email: "team@rspace.online", description: "Internal team communications", thread_count: 4, unread_count: 1 },
			{ slug: "support", name: "Support", email: "support@rspace.online", description: "Community support requests with multi-sig approval", thread_count: 3, unread_count: 1 },
		];
		this.render();
	}

	private async loadMailboxes() {
		try {
			const base = window.location.pathname.replace(/\/$/, "");
			const token = getAccessToken();
			const resp = await fetch(`${base}/api/mailboxes`, {
				headers: token ? { Authorization: `Bearer ${token}` } : {},
			});
			if (resp.ok) {
				const data = await resp.json();
				const list = data.mailboxes || [];
				// Mark space mailbox as primary
				const spaceEmail = `${this.space}@rspace.online`;
				for (const m of list) {
					if (m.email === spaceEmail) m.primary = true;
				}
				this.mailboxes = list;
			}
		} catch { /* ignore */ }

		if (this.mailboxes.length === 0) {
			this.showingSampleData = true;
			this.loadDemoData();
			return;
		}
		this.render();
	}

	private async loadThreads(slug: string) {
		if (this.space === "demo" || this.showingSampleData) {
			this.threads = this.demoThreads[slug] || [];
			if (this.filter !== "all") this.threads = this.threads.filter(t => t.status === this.filter);
			this.render();
			return;
		}
		try {
			const base = window.location.pathname.replace(/\/$/, "");
			const status = this.filter === "all" ? "" : `?status=${this.filter}`;
			const resp = await fetch(`${base}/api/mailboxes/${slug}/threads${status}`);
			if (resp.ok) {
				const data = await resp.json();
				this.threads = data.threads || [];
			}
		} catch { /* ignore */ }
		this.render();
	}

	private async loadThread(id: string) {
		if (this.space === "demo" || this.showingSampleData) {
			const all = [...(this.demoThreads.team || []), ...(this.demoThreads.support || [])];
			this.currentThread = all.find(t => t.id === id) || null;
			if (this.currentThread) {
				this.currentThread.comments = this.currentThread.comments || [{ username: "Team Bot", body: "Thread noted.", created_at: new Date().toISOString() }];
			}
			this.render();
			return;
		}
		try {
			const base = window.location.pathname.replace(/\/$/, "");
			const resp = await fetch(`${base}/api/threads/${id}`);
			if (resp.ok) {
				this.currentThread = await resp.json();
			}
		} catch { /* ignore */ }
		this.render();
	}

	private async loadApprovals() {
		if (this.space === "demo" || this.showingSampleData) { this.approvals = this.demoApprovals; this.render(); return; }
		try {
			const base = window.location.pathname.replace(/\/$/, "");
			const q = this.currentMailbox ? `?mailbox=${this.currentMailbox.slug}` : "";
			const resp = await fetch(`${base}/api/approvals${q}`);
			if (resp.ok) {
				const data = await resp.json();
				this.approvals = data.approvals || [];
			}
		} catch { /* ignore */ }
		this.render();
	}

	private async loadPersonalInboxes() {
		if (this.space === "demo" || this.showingSampleData) {
			this.personalInboxes = [
				{ id: "pi1", label: "Gmail", email: "user@gmail.com", status: "active", last_sync_at: new Date(Date.now() - 60000).toISOString() },
			];
			this.render();
			return;
		}
		try {
			const base = window.location.pathname.replace(/\/$/, "");
			const token = getAccessToken();
			const resp = await fetch(`${base}/api/personal-inboxes`, {
				headers: token ? { Authorization: `Bearer ${token}` } : {},
			});
			if (resp.ok) {
				const data = await resp.json();
				this.personalInboxes = data.personal_inboxes || [];
			}
		} catch { /* ignore */ }
		this.render();
	}

	private async loadAgentInboxes() {
		if (this.space === "demo" || this.showingSampleData) {
			this.agentInboxes = [
				{ id: "ai1", name: "Support Bot", email: "support-bot@rspace.online", auto_reply: true, auto_classify: true, rules: [{ match: { field: 'subject', pattern: 'urgent' }, action: 'tag', value: 'urgent' }] },
			];
			this.render();
			return;
		}
		try {
			const base = window.location.pathname.replace(/\/$/, "");
			const resp = await fetch(`${base}/api/agent-inboxes?space=${this.space}`);
			if (resp.ok) {
				const data = await resp.json();
				this.agentInboxes = data.agent_inboxes || [];
			}
		} catch { /* ignore */ }
		this.render();
	}

	private getAuthHeaders(): Record<string, string> {
		const headers: Record<string, string> = { "Content-Type": "application/json" };
		const token = getAccessToken();
		if (token) headers["Authorization"] = `Bearer ${token}`;
		return headers;
	}

	private async _checkForwardStatus() {
		const token = getAccessToken();
		if (!token) { this._fwdStatus = 'unavailable'; return; }
		try {
			const resp = await fetch('https://auth.rspace.online/api/account/email-forward', {
				headers: { Authorization: `Bearer ${token}` },
			});
			if (!resp.ok) { this._fwdStatus = 'unavailable'; return; }
			const data = await resp.json();
			if (data.enabled) {
				this._fwdStatus = 'enabled';
				this._fwdAddress = data.address || '';
				this._fwdTarget = data.forwardsTo || '';
			} else if (data.available && data.forwardsTo) {
				this._fwdStatus = 'ready';
				this._fwdAddress = data.address || '';
				this._fwdTarget = data.forwardsTo || '';
			} else if (!data.forwardsTo) {
				this._fwdStatus = 'no-email';
				this._fwdAddress = data.address || '';
			} else {
				this._fwdStatus = 'unavailable';
			}
		} catch {
			this._fwdStatus = 'unavailable';
		}
		this.render();
	}

	private async _enableForwarding() {
		if (this._fwdBusy) return;
		this._fwdBusy = true;
		this._fwdError = '';
		this.render();
		const token = getAccessToken();
		try {
			const resp = await fetch('https://auth.rspace.online/api/account/email-forward/enable', {
				method: 'POST',
				headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
			});
			if (resp.ok) {
				this._fwdStatus = 'enabled';
			} else {
				const err = await resp.json().catch(() => ({}));
				this._fwdError = (err as any).error || 'Failed to enable forwarding';
				this._fwdStatus = 'error';
			}
		} catch {
			this._fwdError = 'Network error — try again';
			this._fwdStatus = 'error';
		}
		this._fwdBusy = false;
		this.render();
	}

	private async _disableForwarding() {
		if (this._fwdBusy) return;
		this._fwdBusy = true;
		this.render();
		const token = getAccessToken();
		try {
			const resp = await fetch('https://auth.rspace.online/api/account/email-forward/disable', {
				method: 'POST',
				headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
			});
			if (resp.ok) {
				this._fwdStatus = 'ready';
			}
		} catch { /* ignore */ }
		this._fwdBusy = false;
		this.render();
	}

	private async _saveEmailAndEnable() {
		if (this._fwdBusy) return;
		const input = this.shadow.getElementById('fwd-email-input') as HTMLInputElement | null;
		const email = input?.value.trim();
		if (!email) return;
		this._fwdBusy = true;
		this._fwdError = '';
		this.render();
		const token = getAccessToken();
		try {
			// Save email to profile
			const profileResp = await fetch('https://auth.rspace.online/api/user/profile', {
				method: 'PUT',
				headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
				body: JSON.stringify({ profileEmail: email }),
			});
			if (!profileResp.ok) {
				const err = await profileResp.json().catch(() => ({}));
				this._fwdError = (err as any).error || 'Failed to save email';
				this._fwdStatus = 'error';
				this._fwdBusy = false;
				this.render();
				return;
			}
			// Enable forwarding
			const fwdResp = await fetch('https://auth.rspace.online/api/account/email-forward/enable', {
				method: 'POST',
				headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
			});
			if (fwdResp.ok) {
				this._fwdStatus = 'enabled';
				this._fwdTarget = email;
			} else {
				const err = await fwdResp.json().catch(() => ({}));
				this._fwdError = (err as any).error || 'Failed to enable forwarding';
				this._fwdStatus = 'error';
			}
		} catch {
			this._fwdError = 'Network error — try again';
			this._fwdStatus = 'error';
		}
		this._fwdBusy = false;
		this.render();
	}

	private renderForwardTrigger(): string {
		if (this._fwdStatus === 'loading' || this._fwdStatus === 'unavailable') return '';

		if (this._fwdStatus === 'enabled') {
			return `
				<div class="fwd-trigger fwd-trigger-enabled" data-action="fwd-open">
					<span class="fwd-trigger-dot fwd-dot-active"></span>
					<span>Forwarding active: <code>${this.escapeHtml(this._fwdAddress)}</code> &rarr; <code>${this.escapeHtml(this._fwdTarget)}</code></span>
					<span class="fwd-trigger-link">Manage</span>
				</div>`;
		}

		return `
			<div class="fwd-trigger" data-action="fwd-open">
				<span class="fwd-trigger-icon">&#128233;</span>
				<span class="fwd-trigger-link">Forward rInbox to your email &rarr;</span>
			</div>`;
	}

	private renderForwardModal(): string {
		if (!this._fwdModalOpen) return '';

		let content = '';

		if (this._fwdStatus === 'ready') {
			content = `
				<div class="fwd-modal-status">
					<div class="fwd-modal-route">
						<div class="fwd-modal-route-line">
							<code>${this.escapeHtml(this._fwdAddress)}</code>
							<span class="fwd-arrow">&rarr;</span>
							<code>${this.escapeHtml(this._fwdTarget)}</code>
						</div>
					</div>
					<p class="fwd-modal-desc">Mail sent to your rInbox address will be forwarded to your personal email.</p>
					<div class="fwd-sovereignty">
						&#128274; Your email is stored locally with your EncryptID keys — sovereign and encrypted on your device.
					</div>
				</div>
				<div class="fwd-modal-actions">
					<button class="fwd-btn-enable" data-action="fwd-enable" ${this._fwdBusy ? 'disabled' : ''}>${this._fwdBusy ? 'Enabling...' : 'Enable Forwarding'}</button>
					<button class="fwd-btn-cancel" data-action="fwd-close">Cancel</button>
				</div>`;
		} else if (this._fwdStatus === 'enabled') {
			content = `
				<div class="fwd-modal-status">
					<div class="fwd-modal-status-badge fwd-status-active">
						<span class="fwd-trigger-dot fwd-dot-active"></span>
						Forwarding Active
					</div>
					<div class="fwd-modal-route">
						<div class="fwd-modal-route-line">
							<code>${this.escapeHtml(this._fwdAddress)}</code>
							<span class="fwd-arrow">&rarr;</span>
							<code>${this.escapeHtml(this._fwdTarget)}</code>
						</div>
					</div>
					<div class="fwd-sovereignty">
						&#128274; Your email is stored locally with your EncryptID keys — sovereign and encrypted on your device.
					</div>
				</div>
				<div class="fwd-modal-actions">
					<button class="fwd-btn-disable" data-action="fwd-disable" ${this._fwdBusy ? 'disabled' : ''}>${this._fwdBusy ? 'Disabling...' : 'Disable Forwarding'}</button>
					<button class="fwd-btn-cancel" data-action="fwd-close">Close</button>
				</div>`;
		} else if (this._fwdStatus === 'no-email') {
			content = `
				<div class="fwd-modal-status">
					<p class="fwd-modal-desc">Connect an email address to receive forwarded mail from your rInbox.</p>
					<div class="fwd-sovereignty">
						&#128274; Your email is stored locally with your EncryptID keys — never on a central server. Only you control your contact info.
					</div>
					<div class="compose-field" style="margin-top:1rem">
						<label>Your email address</label>
						<input id="fwd-email-input" type="email" placeholder="you@example.com" />
					</div>
				</div>
				<div class="fwd-modal-actions">
					<button class="fwd-btn-enable" data-action="fwd-save-enable" ${this._fwdBusy ? 'disabled' : ''}>${this._fwdBusy ? 'Saving...' : 'Save &amp; Enable'}</button>
					<button class="fwd-btn-cancel" data-action="fwd-close">Cancel</button>
				</div>`;
		} else if (this._fwdStatus === 'error') {
			content = `
				<div class="fwd-modal-status">
					<div class="fwd-modal-error">${this.escapeHtml(this._fwdError)}</div>
				</div>
				<div class="fwd-modal-actions">
					<button class="fwd-btn-enable" data-action="fwd-enable">Retry</button>
					<button class="fwd-btn-cancel" data-action="fwd-close">Close</button>
				</div>`;
		}

		return `
			<div class="fwd-overlay" data-action="fwd-close-overlay">
				<div class="fwd-modal">
					<button class="help-close" data-action="fwd-close">&times;</button>
					<div class="fwd-modal-title">&#128233; Email Forwarding</div>
					${content}
				</div>
			</div>`;
	}

	private timeAgo(dateStr: string): string {
		const diff = Date.now() - new Date(dateStr).getTime();
		const mins = Math.floor(diff / 60000);
		if (mins < 60) return `${mins}m ago`;
		const hours = Math.floor(mins / 60);
		if (hours < 24) return `${hours}h ago`;
		const days = Math.floor(hours / 24);
		return `${days}d ago`;
	}

	private snippet(text: string, len = 80): string {
		if (!text) return "";
		const s = text.replace(/\n+/g, " ").trim();
		return s.length > len ? s.slice(0, len) + "..." : s;
	}

	private render() {
		this.destroyEditors();
		this.shadow.innerHTML = `
			<style>
				:host { display: block; min-height: 60vh; font-family: system-ui, sans-serif; color: var(--rs-text-primary); -webkit-tap-highlight-color: transparent; }
				button, a, input, select, textarea, [role="button"] { touch-action: manipulation; }
				.container { max-width: 1000px; margin: 0 auto; }

				/* Nav */
				.rapp-nav { display: flex; gap: 0.5rem; margin-bottom: 1rem; align-items: center; min-height: 36px; flex-wrap: wrap; }
				.rapp-nav__back { padding: 4px 10px; border-radius: 6px; border: 1px solid var(--rs-border); background: transparent; color: var(--rs-text-secondary); cursor: pointer; font-size: 0.8rem; }
				.rapp-nav__back:hover { color: var(--rs-text-primary); border-color: var(--rs-border-strong); }
				.nav-btn { padding: 0.4rem 1rem; border-radius: 8px; border: 1px solid var(--rs-border-strong); background: transparent; color: var(--rs-text-secondary); cursor: pointer; font-size: 0.8rem; transition: all 0.15s; }
				.nav-btn.active { background: var(--rs-primary); color: white; border-color: var(--rs-primary); }
				.nav-btn:hover:not(.active) { border-color: var(--rs-border-strong); color: var(--rs-text-primary); }
				.nav-spacer { flex: 1; }
				.help-btn { padding: 4px 10px; border-radius: 6px; border: 1px solid rgba(99,102,241,0.3); background: rgba(99,102,241,0.1); color: #818cf8; cursor: pointer; font-size: 0.75rem; transition: all 0.15s; }
				.help-btn:hover { background: rgba(99,102,241,0.2); border-color: rgba(99,102,241,0.5); }

				/* Mailbox cards */
				.mailbox-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 0.75rem; }
				.mailbox-card { background: var(--rs-bg-surface); border: 1px solid var(--rs-border); border-radius: 12px; padding: 1.25rem; cursor: pointer; transition: all 0.2s; }
				.mailbox-card:hover { border-color: var(--rs-primary); transform: translateY(-1px); box-shadow: 0 4px 16px rgba(99,102,241,0.15); }
				.mailbox-primary { border-color: #6366f1; background: linear-gradient(135deg, var(--rs-bg-surface), rgba(99,102,241,0.08)); grid-column: 1 / -1; }
				.mailbox-primary .mailbox-name { font-size: 1.1rem; color: #818cf8; }
				.mailbox-icon { font-size: 1.5rem; margin-bottom: 0.75rem; }
				.mailbox-name { font-weight: 600; font-size: 1rem; margin-bottom: 0.25rem; color: var(--rs-text-primary); }
				.mailbox-email { font-size: 0.8rem; color: #6366f1; margin-bottom: 0.5rem; font-family: monospace; }
				.mailbox-desc { font-size: 0.85rem; color: var(--rs-text-secondary); line-height: 1.5; margin-bottom: 0.75rem; }
				.mailbox-stats { display: flex; gap: 1rem; font-size: 0.75rem; color: var(--rs-text-muted); }
				.mailbox-stats .unread { color: #818cf8; font-weight: 600; }

				/* Inbox list container */
				.inbox-list { background: var(--rs-bg-surface); border: 1px solid var(--rs-border-strong); border-radius: 12px; overflow: hidden; box-shadow: var(--rs-shadow-md); }

				/* Inbox header */
				.inbox-header { display: flex; align-items: center; gap: 0.75rem; padding: 0.85rem 1rem; border-bottom: 1px solid var(--rs-border); }
				.inbox-header-icon { font-size: 1.1rem; }
				.inbox-header-name { font-weight: 600; font-size: 0.95rem; color: var(--rs-text-primary); }
				.inbox-header-email { font-size: 0.75rem; color: var(--rs-text-muted); font-family: monospace; }
				.inbox-header-spacer { flex: 1; }
				.inbox-unread-badge { display: inline-flex; align-items: center; justify-content: center; min-width: 22px; height: 22px; padding: 0 6px; border-radius: 11px; background: var(--rs-primary); color: white; font-size: 0.7rem; font-weight: 700; }

				/* Filter bar */
				.filter-bar { display: flex; gap: 0.25rem; padding: 0.75rem 1rem; border-bottom: 1px solid var(--rs-border); }
				.filter-btn { padding: 6px 12px; min-height: 36px; border-radius: 6px; border: none; background: transparent; color: var(--rs-text-muted); cursor: pointer; font-size: 0.75rem; transition: all 0.15s; touch-action: manipulation; }
				.filter-btn:hover, .filter-btn:active { color: var(--rs-text-secondary); }
				.filter-btn.active { background: rgba(99,102,241,0.15); color: #818cf8; }

				/* Thread rows */
				.thread-row { display: flex; gap: 0.75rem; align-items: flex-start; padding: 0.85rem 1rem; border-bottom: 1px solid var(--rs-border-subtle); cursor: pointer; transition: background 0.15s; border-left: 3px solid transparent; }
				.thread-row:last-child { border-bottom: none; }
				.thread-row:hover { background: var(--rs-bg-hover); }

				/* Unread row */
				.thread-row.unread { border-left-color: var(--rs-primary); background: var(--rs-bg-active); }
				.thread-row.unread .thread-from { font-weight: 600; color: var(--rs-text-primary); }
				.thread-row.unread .thread-subject { font-weight: 600; color: var(--rs-text-primary); }

				/* Read row */
				.thread-row:not(.unread):not(.attention) .thread-from { color: var(--rs-text-secondary); }
				.thread-row:not(.unread):not(.attention) .thread-subject { color: var(--rs-text-secondary); }

				/* Needs-attention row */
				.thread-row.attention { border-left-color: var(--rs-warning); background: rgba(251,191,36,0.06); }
				.thread-row.attention .thread-from { font-weight: 600; color: var(--rs-text-primary); }
				.thread-row.attention .thread-subject { font-weight: 600; color: var(--rs-text-primary); }

				.thread-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--rs-primary); flex-shrink: 0; margin-top: 0.45rem; }
				.thread-dot.read { background: transparent; }

				/* Avatar circle */
				.thread-avatar { width: 32px; height: 32px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 0.7rem; font-weight: 700; color: white; flex-shrink: 0; background: var(--rs-primary); }

				.thread-content { flex: 1; min-width: 0; }
				.thread-top { display: flex; justify-content: space-between; align-items: baseline; gap: 0.5rem; margin-bottom: 0.15rem; }
				.thread-from { font-size: 0.85rem; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--rs-text-primary); }
				.thread-time { font-size: 0.7rem; color: var(--rs-text-muted); flex-shrink: 0; }
				.thread-subject { font-size: 0.85rem; color: var(--rs-text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-bottom: 0.15rem; }
				.thread-snippet { font-size: 0.75rem; color: var(--rs-text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
				.thread-tags { display: flex; gap: 0.35rem; margin-top: 0.35rem; align-items: center; flex-wrap: wrap; }

				/* Badges */
				.badge { display: inline-block; padding: 0.1rem 0.45rem; border-radius: 4px; font-size: 0.65rem; font-weight: 600; }
				.badge-open { background: rgba(34,197,94,0.15); color: #4ade80; }
				.badge-snoozed { background: rgba(251,191,36,0.15); color: #fbbf24; }
				.badge-closed { background: rgba(100,116,139,0.15); color: var(--rs-text-secondary); }
				.badge-pending { background: rgba(251,191,36,0.15); color: #fbbf24; }
				.badge-approved { background: rgba(34,197,94,0.15); color: #4ade80; }
				.badge-sent { background: rgba(34,197,94,0.15); color: #4ade80; }
				.badge-comments { background: rgba(99,102,241,0.15); color: #818cf8; }
				.badge-outbound { background: rgba(96,165,250,0.15); color: #60a5fa; }
				.badge-bot { background: rgba(168,85,247,0.15); color: #a855f7; }
				.badge-replied { background: rgba(34,197,94,0.12); color: #4ade80; }
				.badge-forwarded { background: rgba(96,165,250,0.12); color: #60a5fa; }
				.badge-attention { background: rgba(251,191,36,0.15); color: #f59e0b; font-weight: 700; }
				.star { color: #fbbf24; font-size: 0.7rem; }

				/* Thread detail */
				.detail-panel { background: var(--rs-bg-surface); border: 1px solid var(--rs-border-strong); border-radius: 12px; padding: 1.5rem; box-shadow: var(--rs-shadow-md); }
				.detail-header { margin-bottom: 1rem; padding-bottom: 1rem; border-bottom: 1px solid var(--rs-border); }
				.detail-subject { font-size: 1.25rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--rs-text-primary); }
				.detail-meta { font-size: 0.8rem; color: var(--rs-text-muted); display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
				.detail-body { font-size: 0.9rem; line-height: 1.6; color: var(--rs-text-secondary); margin-bottom: 1.5rem; }
				.detail-body p { margin: 0 0 0.5rem; }
				.detail-body p:last-child { margin-bottom: 0; }

				/* Thread actions */
				.thread-actions { display: flex; gap: 0.5rem; margin-bottom: 1.5rem; padding: 0.75rem 0; border-top: 1px solid var(--rs-border); border-bottom: 1px solid var(--rs-border); }
				.thread-actions button { padding: 0.4rem 1rem; border-radius: 6px; border: 1px solid var(--rs-border-strong); background: transparent; color: var(--rs-text-secondary); cursor: pointer; font-size: 0.8rem; transition: all 0.15s; }
				.thread-actions button:hover { border-color: var(--rs-primary); color: #818cf8; background: rgba(99,102,241,0.1); }

				/* Comments */
				.comments-section { border-top: 1px solid var(--rs-border); padding-top: 1rem; }
				.comments-title { font-size: 0.85rem; font-weight: 600; margin-bottom: 0.75rem; color: var(--rs-text-secondary); }
				.comment { padding: 0.75rem; background: var(--rs-bg-surface-sunken); border-radius: 8px; margin-bottom: 0.5rem; border-left: 3px solid var(--rs-border-strong); }
				.comment-top { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 0.25rem; }
				.comment-author { font-size: 0.8rem; font-weight: 600; color: #818cf8; }
				.comment-body { font-size: 0.85rem; color: var(--rs-text-secondary); line-height: 1.5; }
				.comment-time { font-size: 0.7rem; color: var(--rs-text-muted); }
				.comment-body p { margin: 0 0 0.4rem; }
				.comment-body p:last-child { margin-bottom: 0; }
				.comment-body code { font-size: 0.8rem; background: rgba(99,102,241,0.1); padding: 0.1rem 0.3rem; border-radius: 3px; }
				.comment-body blockquote { margin: 0.25rem 0; padding-left: 0.5rem; border-left: 2px solid var(--rs-border-strong); color: var(--rs-text-muted); }
				.comment-input { margin-top: 0.75rem; }
				.comment-input-editor { background: var(--rs-input-bg); border: 1px solid var(--rs-input-border); border-radius: 8px; padding: 0.5rem 0.75rem; min-height: 48px; transition: border-color 0.15s; }
				.comment-input-editor:focus-within { border-color: var(--rs-primary); }
				.comment-input-actions { display: flex; gap: 0.5rem; margin-top: 0.5rem; justify-content: flex-end; }
				.btn-comment { padding: 0.35rem 1rem; border-radius: 6px; border: none; background: var(--rs-primary); color: white; cursor: pointer; font-size: 0.8rem; font-weight: 500; transition: opacity 0.15s; }
				.btn-comment:hover { opacity: 0.9; }
				.empty { text-align: center; padding: 3rem; color: var(--rs-text-muted); }

				/* TipTap editor */
				.ProseMirror { outline: none; min-height: 80px; font-size: 0.85rem; line-height: 1.6; color: var(--rs-input-text); }
				.ProseMirror p { margin: 0 0 0.5rem; }
				.ProseMirror p:last-child { margin-bottom: 0; }
				.ProseMirror h1, .ProseMirror h2, .ProseMirror h3 { margin: 0.75rem 0 0.25rem; font-weight: 600; color: var(--rs-text-primary); }
				.ProseMirror h1 { font-size: 1.3rem; }
				.ProseMirror h2 { font-size: 1.1rem; }
				.ProseMirror h3 { font-size: 0.95rem; }
				.ProseMirror ul, .ProseMirror ol { padding-left: 1.5rem; margin: 0.25rem 0; }
				.ProseMirror li { margin-bottom: 0.15rem; }
				.ProseMirror blockquote { margin: 0.5rem 0; padding-left: 0.75rem; border-left: 3px solid var(--rs-border-strong); color: var(--rs-text-secondary); }
				.ProseMirror code { font-size: 0.82rem; background: rgba(99,102,241,0.1); padding: 0.1rem 0.35rem; border-radius: 4px; font-family: monospace; }
				.ProseMirror pre { background: var(--rs-bg-surface-sunken); border-radius: 8px; padding: 0.75rem; margin: 0.5rem 0; overflow-x: auto; }
				.ProseMirror pre code { background: none; padding: 0; border-radius: 0; font-size: 0.8rem; }
				.ProseMirror hr { border: none; border-top: 1px solid var(--rs-border); margin: 0.75rem 0; }
				.ProseMirror a { color: #818cf8; text-decoration: underline; }
				.ProseMirror ul[data-type="taskList"] { list-style: none; padding-left: 0; }
				.ProseMirror ul[data-type="taskList"] li { display: flex; align-items: flex-start; gap: 0.35rem; }
				.ProseMirror ul[data-type="taskList"] li > label { margin-top: 0.15rem; }
				.ProseMirror .placeholder::before { content: attr(data-placeholder); color: var(--rs-text-muted); pointer-events: none; position: absolute; opacity: 0.6; }
				.comment-input-editor .ProseMirror { min-height: 32px; }

				/* Compose toolbar */
				.compose-toolbar { display: flex; gap: 2px; padding: 0.35rem 0; margin-bottom: 0.35rem; border-bottom: 1px solid var(--rs-border); flex-wrap: wrap; }
				.compose-toolbar button { width: 28px; height: 28px; border-radius: 4px; border: none; background: transparent; color: var(--rs-text-muted); cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.12s; font-size: 0.75rem; font-weight: 700; }
				.compose-toolbar button:hover { background: rgba(99,102,241,0.12); color: var(--rs-text-primary); }
				.compose-toolbar button.active { background: rgba(99,102,241,0.2); color: #818cf8; }
				.compose-toolbar .toolbar-sep { width: 1px; height: 20px; background: var(--rs-border); margin: 4px 3px; flex-shrink: 0; }

				/* Collab cursor */
				.collab-cursor { border-left: 2px solid; position: relative; margin-left: -1px; margin-right: -1px; pointer-events: none; }
				.collab-cursor-label { position: absolute; top: -1.4em; left: -1px; padding: 1px 6px; border-radius: 3px; font-size: 0.65rem; white-space: nowrap; color: white; pointer-events: none; user-select: none; }

				/* Markdown body rendering */
				.detail-body h1, .detail-body h2, .detail-body h3, .detail-body h4 { margin: 0.75rem 0 0.25rem; font-weight: 600; color: var(--rs-text-primary); }
				.detail-body h1 { font-size: 1.4rem; }
				.detail-body h2 { font-size: 1.15rem; }
				.detail-body h3 { font-size: 1rem; }
				.detail-body ul, .detail-body ol { padding-left: 1.5rem; margin: 0.25rem 0; }
				.detail-body blockquote { margin: 0.5rem 0; padding-left: 0.75rem; border-left: 3px solid var(--rs-border-strong); color: var(--rs-text-muted); }
				.detail-body code { font-size: 0.82rem; background: rgba(99,102,241,0.1); padding: 0.1rem 0.35rem; border-radius: 4px; font-family: monospace; }
				.detail-body pre { background: var(--rs-bg-surface-sunken); border-radius: 8px; padding: 0.75rem; margin: 0.5rem 0; overflow-x: auto; }
				.detail-body pre code { background: none; padding: 0; border-radius: 0; }
				.detail-body a { color: #818cf8; text-decoration: underline; }
				.detail-body table { border-collapse: collapse; margin: 0.5rem 0; width: 100%; font-size: 0.85rem; }
				.detail-body th, .detail-body td { border: 1px solid var(--rs-border); padding: 0.35rem 0.65rem; text-align: left; }
				.detail-body th { background: var(--rs-bg-surface-sunken); font-weight: 600; }
				.detail-body img { max-width: 100%; border-radius: 6px; }

				/* Approval cards */
				.approval-card { background: var(--rs-bg-surface); border: 1px solid var(--rs-border); border-radius: 12px; padding: 1.25rem; margin-bottom: 0.75rem; border-left: 4px solid var(--rs-border-strong); transition: border-color 0.2s; }
				.approval-card.status-pending { border-left-color: #fbbf24; }
				.approval-card.status-approved, .approval-card.status-sent { border-left-color: #22c55e; }
				.approval-card.status-rejected { border-left-color: #ef4444; }
				.approval-to { font-size: 0.8rem; color: var(--rs-text-muted); margin-bottom: 0.5rem; }
				.approval-to code { font-size: 0.75rem; color: #818cf8; background: rgba(99,102,241,0.1); padding: 0.1rem 0.4rem; border-radius: 4px; }
				.approval-body-preview { font-size: 0.8rem; color: var(--rs-text-secondary); line-height: 1.5; padding: 0.75rem; background: var(--rs-bg-surface-sunken); border-radius: 8px; margin: 0.5rem 0; border-left: 3px solid var(--rs-border-strong); max-height: 4.5em; overflow: hidden; }
				.approval-progress { display: flex; align-items: center; gap: 0.5rem; margin-top: 0.75rem; }
				.progress-bar { flex: 1; height: 8px; background: var(--rs-bg-surface-sunken); border-radius: 4px; overflow: hidden; }
				.progress-fill { height: 100%; border-radius: 4px; transition: width 0.3s; }
				.progress-fill.pending { background: linear-gradient(90deg, #fbbf24, #f59e0b); }
				.progress-fill.approved, .progress-fill.sent { background: linear-gradient(90deg, #22c55e, #16a34a); }
				.progress-text { font-size: 0.75rem; color: var(--rs-text-secondary); flex-shrink: 0; }
				.signer-list { margin-top: 0.75rem; display: flex; flex-direction: column; gap: 0.35rem; }
				.signer-row { display: flex; align-items: center; gap: 0.5rem; font-size: 0.8rem; }
				.signer-avatar { width: 26px; height: 26px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 0.7rem; font-weight: 700; color: white; flex-shrink: 0; }
				.signer-avatar.signed { background: #22c55e; }
				.signer-avatar.waiting { background: var(--rs-bg-surface-raised); }
				.signer-name { flex: 1; }
				.signer-name.signed { color: var(--rs-text-primary); }
				.signer-name.waiting { color: var(--rs-text-muted); }
				.signer-status { font-size: 0.7rem; font-weight: 600; }
				.signer-status.signed { color: #4ade80; }
				.signer-status.waiting { color: var(--rs-text-muted); }
				.approval-actions { display: flex; gap: 0.5rem; margin-top: 0.75rem; }
				.btn-approve { padding: 0.4rem 1rem; border-radius: 6px; border: none; background: #22c55e; color: white; cursor: pointer; font-size: 0.8rem; font-weight: 500; transition: background 0.15s; }
				.btn-approve:hover { background: #16a34a; }
				.btn-reject { padding: 0.4rem 1rem; border-radius: 6px; border: none; background: #ef4444; color: white; cursor: pointer; font-size: 0.8rem; font-weight: 500; transition: background 0.15s; }
				.btn-reject:hover { background: #dc2626; }
				.btn-compose { padding: 0.5rem 1.25rem; border-radius: 8px; border: 1px solid var(--rs-primary); background: rgba(99,102,241,0.1); color: #818cf8; cursor: pointer; font-size: 0.85rem; font-weight: 600; transition: all 0.15s; margin-bottom: 1rem; }
				.btn-compose:hover { background: rgba(99,102,241,0.2); border-color: #818cf8; }

				/* Compose form */
				.compose-panel { background: var(--rs-bg-surface); border: 1px solid var(--rs-border-strong); border-radius: 12px; padding: 1.25rem; margin-bottom: 1rem; }
				.compose-panel h3 { font-size: 1rem; font-weight: 600; margin-bottom: 1rem; display: flex; align-items: center; gap: 0.5rem; color: var(--rs-text-primary); }
				.compose-field { margin-bottom: 0.75rem; }
				.compose-field label { display: block; font-size: 0.75rem; color: var(--rs-text-secondary); margin-bottom: 0.25rem; font-weight: 500; }
				.compose-field input, .compose-field textarea { width: 100%; background: var(--rs-input-bg); border: 1px solid var(--rs-input-border); border-radius: 8px; padding: 0.5rem 0.75rem; color: var(--rs-input-text); font-size: 0.85rem; font-family: inherit; outline: none; transition: border-color 0.15s; box-sizing: border-box; }
				.compose-field input:focus, .compose-field textarea:focus { border-color: var(--rs-primary); }
				.compose-field textarea { resize: vertical; min-height: 100px; }
				.compose-threshold { font-size: 0.8rem; color: var(--rs-text-secondary); padding: 0.6rem 0.75rem; background: rgba(99,102,241,0.08); border: 1px solid rgba(99,102,241,0.2); border-radius: 8px; margin-bottom: 0.75rem; }
				.compose-actions { display: flex; gap: 0.5rem; }
				.btn-submit { padding: 0.5rem 1.25rem; border-radius: 8px; border: none; background: linear-gradient(135deg, #6366f1, #0891b2); color: white; cursor: pointer; font-size: 0.85rem; font-weight: 600; transition: opacity 0.15s; }
				.btn-submit:hover { opacity: 0.9; }
				.btn-cancel { padding: 0.5rem 1.25rem; border-radius: 8px; border: 1px solid var(--rs-border-strong); background: transparent; color: var(--rs-text-secondary); cursor: pointer; font-size: 0.85rem; transition: all 0.15s; }
				.btn-cancel:hover { border-color: var(--rs-border-strong); color: var(--rs-text-primary); }

				/* Personal / Agent cards */
				.inbox-card { background: var(--rs-bg-surface); border: 1px solid var(--rs-border); border-radius: 12px; padding: 1rem; margin-bottom: 0.75rem; display: flex; align-items: center; gap: 1rem; }
				.inbox-card-info { flex: 1; }
				.inbox-card-label { font-weight: 600; font-size: 0.9rem; color: var(--rs-text-primary); }
				.inbox-card-email { font-size: 0.8rem; color: #6366f1; font-family: monospace; }
				.inbox-card-status { font-size: 0.7rem; }
				.inbox-card-status.active { color: #4ade80; }
				.inbox-card-status.error { color: #ef4444; }
				.inbox-card-status.paused { color: #fbbf24; }
				.inbox-card-actions button { padding: 0.3rem 0.75rem; border-radius: 6px; border: 1px solid var(--rs-border-strong); background: transparent; color: var(--rs-text-secondary); cursor: pointer; font-size: 0.75rem; }
				.inbox-card-actions button:hover { border-color: #ef4444; color: #ef4444; }
				.agent-toggle { font-size: 0.75rem; color: var(--rs-text-secondary); margin-top: 0.25rem; }
				.agent-toggle .on { color: #4ade80; }
				.agent-toggle .off { color: var(--rs-text-muted); }

				/* Help panel */
				.help-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.6); z-index: 10000; display: flex; align-items: center; justify-content: center; padding: 1rem; }
				.help-panel { background: var(--rs-bg-surface-sunken); border: 1px solid var(--rs-border-strong); border-radius: 16px; max-width: 640px; width: 100%; max-height: 80vh; overflow-y: auto; padding: 2rem; position: relative; }
				.help-close { position: absolute; top: 1rem; right: 1rem; background: none; border: none; color: var(--rs-text-muted); cursor: pointer; font-size: 1.25rem; padding: 4px 8px; border-radius: 6px; }
				.help-close:hover { color: var(--rs-text-primary); background: var(--rs-bg-hover); }
				.help-title { font-size: 1.5rem; font-weight: 700; margin-bottom: 0.5rem; color: var(--rs-text-primary); }
				.help-subtitle { color: var(--rs-text-secondary); font-size: 0.9rem; margin-bottom: 1.5rem; line-height: 1.5; }
				.help-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; margin-bottom: 1.5rem; }
				.help-card { background: var(--rs-bg-surface); border: 1px solid var(--rs-border-strong); border-radius: 10px; padding: 1rem; }
				.help-card-icon { font-size: 1.25rem; margin-bottom: 0.5rem; }
				.help-card h4 { font-size: 0.85rem; font-weight: 600; margin-bottom: 0.25rem; color: var(--rs-text-primary); }
				.help-card p { font-size: 0.75rem; color: var(--rs-text-secondary); line-height: 1.4; }
				.help-section { margin-bottom: 1.25rem; }
				.help-section h3 { font-size: 1rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--rs-text-primary); }
				.help-steps { display: flex; flex-direction: column; gap: 0.5rem; }
				.help-step { display: flex; gap: 0.75rem; align-items: flex-start; }
				.help-step-num { width: 24px; height: 24px; border-radius: 50%; background: var(--rs-primary); color: white; font-size: 0.7rem; font-weight: 700; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
				.help-step-text { font-size: 0.8rem; color: var(--rs-text-secondary); line-height: 1.5; }
				.help-cta { display: inline-block; padding: 0.6rem 1.5rem; background: linear-gradient(135deg, #6366f1, #0891b2); color: white; border-radius: 8px; text-decoration: none; font-size: 0.85rem; font-weight: 600; }

				.sample-banner { padding: 8px 16px; background: rgba(99,102,241,0.12); border: 1px solid rgba(99,102,241,0.25); border-radius: 8px; color: #a5b4fc; font-size: 13px; text-align: center; margin-bottom: 12px; }

				/* Forwarding trigger */
				.fwd-trigger { display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem 0.75rem; margin-bottom: 12px; cursor: pointer; border-radius: 8px; font-size: 0.8rem; color: var(--rs-text-secondary); transition: background 0.15s; }
				.fwd-trigger:hover { background: var(--rs-bg-hover); }
				.fwd-trigger-enabled { color: #4ade80; }
				.fwd-trigger-enabled code { font-size: 0.75rem; color: #4ade80; background: rgba(34,197,94,0.1); padding: 2px 6px; border-radius: 4px; }
				.fwd-trigger-icon { font-size: 1rem; }
				.fwd-trigger-link { color: #818cf8; font-weight: 500; }
				.fwd-trigger-enabled .fwd-trigger-link { color: rgba(74,222,128,0.7); font-weight: 400; margin-left: auto; }
				.fwd-trigger-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; }
				.fwd-dot-active { background: #4ade80; box-shadow: 0 0 6px rgba(74,222,128,0.5); }

				/* Forwarding modal overlay */
				.fwd-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.6); z-index: 10000; display: flex; align-items: center; justify-content: center; padding: 1rem; }
				.fwd-modal { background: var(--rs-bg-surface-sunken); border: 1px solid var(--rs-border-strong); border-radius: 16px; max-width: 480px; width: 100%; padding: 2rem; position: relative; }
				.fwd-modal-title { font-size: 1.25rem; font-weight: 700; margin-bottom: 1.25rem; color: var(--rs-text-primary); }
				.fwd-modal-status { margin-bottom: 1.25rem; }
				.fwd-modal-status-badge { display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.4rem 0.75rem; border-radius: 6px; font-size: 0.85rem; font-weight: 600; margin-bottom: 0.75rem; }
				.fwd-status-active { background: rgba(34,197,94,0.1); color: #4ade80; }
				.fwd-modal-route { margin: 0.75rem 0; }
				.fwd-modal-route-line { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
				.fwd-modal-route-line code { font-size: 0.8rem; color: #818cf8; background: rgba(99,102,241,0.1); padding: 3px 8px; border-radius: 4px; }
				.fwd-arrow { color: var(--rs-text-muted); font-size: 0.85rem; }
				.fwd-modal-desc { font-size: 0.85rem; color: var(--rs-text-secondary); line-height: 1.5; margin: 0.5rem 0; }
				.fwd-sovereignty { font-size: 0.8rem; color: var(--rs-text-muted); line-height: 1.5; padding: 0.75rem; background: rgba(99,102,241,0.06); border: 1px solid rgba(99,102,241,0.15); border-radius: 8px; margin-top: 0.75rem; }
				.fwd-modal-error { font-size: 0.85rem; color: #f87171; padding: 0.75rem; background: rgba(239,68,68,0.08); border: 1px solid rgba(239,68,68,0.2); border-radius: 8px; }
				.fwd-modal-actions { display: flex; gap: 0.5rem; justify-content: flex-end; }
				.fwd-btn-enable { padding: 8px 18px; border-radius: 8px; border: none; background: linear-gradient(135deg, #6366f1, #0891b2); color: white; cursor: pointer; font-size: 0.85rem; font-weight: 600; transition: opacity 0.15s; }
				.fwd-btn-enable:hover { opacity: 0.9; }
				.fwd-btn-enable:disabled { opacity: 0.5; cursor: not-allowed; }
				.fwd-btn-disable { padding: 8px 18px; border-radius: 8px; border: 1px solid rgba(239,68,68,0.3); background: transparent; color: #f87171; cursor: pointer; font-size: 0.85rem; font-weight: 500; transition: all 0.15s; }
				.fwd-btn-disable:hover { border-color: rgba(239,68,68,0.5); background: rgba(239,68,68,0.08); }
				.fwd-btn-disable:disabled { opacity: 0.5; cursor: not-allowed; }
				.fwd-btn-cancel { padding: 8px 18px; border-radius: 8px; border: 1px solid var(--rs-border-strong); background: transparent; color: var(--rs-text-secondary); cursor: pointer; font-size: 0.85rem; transition: all 0.15s; }
				.fwd-btn-cancel:hover { border-color: var(--rs-border-strong); color: var(--rs-text-primary); }

				@media (max-width: 768px) {
					.mailbox-grid { grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); }
					.help-grid { grid-template-columns: 1fr; }
					.thread-row { flex-wrap: wrap; gap: 4px; }
				}
				@media (max-width: 480px) {
					.fwd-modal { padding: 1.25rem; }
					.rapp-nav { gap: 0.25rem; }
					.nav-btn { padding: 4px 8px; font-size: 12px; }
					.inbox-header { padding: 0.625rem 0.75rem; gap: 0.5rem; }
					.inbox-header-name { font-size: 0.85rem; }
					.thread-row { padding: 0.625rem 0.75rem; }
					.thread-subject { font-size: 0.8125rem; }
					.thread-preview { font-size: 0.75rem; }
					.filter-bar { padding: 0.5rem 0.75rem; gap: 0.125rem; }
					.filter-btn { padding: 4px 8px; font-size: 11px; }
					.help-panel { padding: 1.25rem; }
					.help-title { font-size: 1.25rem; }
					.compose-panel { padding: 12px; }
					.inbox-card { flex-direction: column; align-items: stretch; gap: 0.5rem; padding: 0.75rem; }
				}
			</style>
			<div class="container">
				${this.renderNav()}
				${this.showingSampleData ? '<div class="sample-banner">Showing sample data — create a mailbox to get started</div>' : ''}
				${this.view === 'mailboxes' ? this.renderForwardTrigger() : ''}
				${this.renderView()}
				${this.helpOpen ? this.renderHelp() : ""}
				${this.renderForwardModal()}
			</div>
		`;
		this.bindEvents();
		this._tour.renderOverlay();
	}

	startTour() { this._tour.start(); }

	private renderNav(): string {
		const items: { id: string; label: string }[] = [
			{ id: "mailboxes", label: "Mailboxes" },
			{ id: "approvals", label: "Approvals" },
		];
		if (this.currentMailbox) {
			items.unshift({ id: "threads", label: this.currentMailbox.name });
		}
		return `
			<div class="rapp-nav">
				${this.view !== "mailboxes" && this._history.canGoBack ? `<button class="rapp-nav__back" data-action="back">&larr;</button>` : ""}
				${items.map((i) => `<button class="nav-btn ${this.view === i.id ? "active" : ""}" data-nav="${i.id}">${i.label}</button>`).join("")}
				<span class="nav-spacer"></span>
				<button class="help-btn" data-action="help" title="About rInbox" style="font-size:1rem;padding:2px 6px">&#9432;</button>
			</div>
		`;
	}

	private renderView(): string {
		switch (this.view) {
			case "mailboxes": return this.renderMailboxes();
			case "threads": return this.renderThreads();
			case "thread": return this.renderThreadDetail();
			case "approvals": return this.renderApprovals();
			case "personal": return this.renderPersonal();
			case "agents": return this.renderAgents();
			default: return "";
		}
	}

	private renderMailboxes(): string {
		if (this.mailboxes.length === 0) {
			return `
				<div class="empty">
					<p style="font-size:2.5rem;margin-bottom:1rem">&#128232;</p>
					<p style="font-size:1.1rem;color:var(--rs-text-primary);font-weight:500;margin-bottom:0.5rem">No mailboxes yet</p>
					<p style="font-size:0.85rem;color:var(--rs-text-muted);max-width:400px;margin:0 auto 1.5rem;line-height:1.5">
						Create a shared mailbox for your team. Members can triage, comment on, and co-sign outgoing emails.
					</p>
					<button class="help-btn" data-action="help" style="font-size:0.85rem;padding:0.5rem 1.25rem">Learn how rInbox works</button>
				</div>`;
		}
		// Sort: primary (space) mailbox first
		const sorted = [...this.mailboxes].sort((a, b) => (b.primary ? 1 : 0) - (a.primary ? 1 : 0));
		return `<div class="mailbox-grid">${sorted.map((m) => {
			const demoThreads = this.demoThreads[m.slug] || [];
			const threadCount = m.thread_count ?? demoThreads.length;
			const unreadCount = m.unread_count ?? demoThreads.filter((t: any) => !t.is_read).length;
			const isPrimary = m.primary || m.email === `${this.space}@rspace.online`;
			return `
			<div class="mailbox-card${isPrimary ? " mailbox-primary" : ""}" data-mailbox="${m.slug}">
				<div class="mailbox-icon">&#128232;</div>
				<div class="mailbox-name">${m.name}</div>
				<div class="mailbox-email">${m.email}</div>
				<div class="mailbox-desc">${m.description || "Shared mailbox"}</div>
				<div class="mailbox-stats">
					<span>${threadCount} thread${threadCount !== 1 ? "s" : ""}</span>
					${unreadCount > 0 ? `<span class="unread">${unreadCount} unread</span>` : ""}
				</div>
			</div>`;
		}).join("")}</div>`;
	}

	private renderThreads(): string {
		const filters = ["all", "open", "snoozed", "closed"];
		const unreadCount = this.threads.filter(t => !t.is_read).length;
		const mailboxName = this.currentMailbox?.name || "Inbox";
		const mailboxEmail = this.currentMailbox?.email || "";

		return `
			<div class="inbox-list">
				<div class="inbox-header">
					<span class="inbox-header-icon">&#128232;</span>
					<span class="inbox-header-name">${mailboxName}</span>
					<span class="inbox-header-email">${mailboxEmail}</span>
					<span class="inbox-header-spacer"></span>
					${unreadCount > 0 ? `<span class="inbox-unread-badge">${unreadCount} unread</span>` : ''}
				</div>
				<div class="filter-bar">
					${filters.map((f) => `<button class="filter-btn ${this.filter === f ? "active" : ""}" data-filter="${f}">${f.charAt(0).toUpperCase() + f.slice(1)}</button>`).join("")}
				</div>
				${this.threads.length === 0
					? `<div class="empty"><p>No threads${this.filter !== "all" ? ` with status "${this.filter}"` : ""}</p></div>`
					: this.threads.map((t) => {
						const isUnread = !t.is_read;
						const isAttention = t.needsAttention || (isUnread && t.is_starred);
						const rowClass = isAttention ? "attention" : (isUnread ? "unread" : "");
						const initials = (t.from_name || "?").split(" ").map((w: string) => w[0]).join("").slice(0, 2).toUpperCase();
						// Assign deterministic avatar color from name
						const avatarColors = ['#6366f1', '#0891b2', '#7c3aed', '#2563eb', '#0d9488', '#9333ea', '#4f46e5'];
						const colorIdx = (t.from_name || "").split("").reduce((a: number, c: string) => a + c.charCodeAt(0), 0) % avatarColors.length;
						return `
						<div class="thread-row ${rowClass}" data-thread="${t.id}" data-collab-id="thread:${t.id}">
							<span class="thread-dot ${t.is_read ? "read" : ""}"></span>
							<div class="thread-avatar" style="background:${avatarColors[colorIdx]}">${initials}</div>
							<div class="thread-content">
								<div class="thread-top">
									<span class="thread-from">${t.from_name || t.from_address || "Unknown"}</span>
									<span class="thread-time">${this.timeAgo(t.received_at)}</span>
								</div>
								<div class="thread-subject">${t.subject}</div>
								<div class="thread-snippet">${this.snippet(t.body_text)}</div>
								<div class="thread-tags">
									${t.is_starred ? `<span class="star">&#9733;</span>` : ""}
									<span class="badge badge-${t.status}">${t.status}</span>
									${t.direction === 'outbound' ? `<span class="badge badge-outbound">sent</span>` : ''}
									${t.replied ? `<span class="badge badge-replied">&#8617; Replied</span>` : ''}
									${t.forwarded ? `<span class="badge badge-forwarded">&#8618; Forwarded</span>` : ''}
									${isAttention ? `<span class="badge badge-attention">Action needed</span>` : ''}
									${t.comment_count > 0 ? `<span class="badge badge-comments">&#128172; ${t.comment_count}</span>` : ""}
								</div>
							</div>
						</div>
					`}).join("")}
			</div>
		`;
	}

	private renderThreadDetail(): string {
		if (!this.currentThread) return `<div class="empty">Loading...</div>`;
		const t = this.currentThread;
		const comments = t.comments || [];
		const composeHtml = this.composeOpen ? this.renderInlineCompose() : '';
		return `
			<div class="detail-panel">
				<div class="detail-header">
					<div class="detail-subject">${t.subject}</div>
					<div class="detail-meta">
						<span>From: <strong style="color:var(--rs-text-primary)">${t.from_name || ""}</strong> &lt;${t.from_address || "unknown"}&gt;</span>
						<span>&middot;</span>
						<span>${this.timeAgo(t.received_at)}</span>
						<span>&middot;</span>
						<span class="badge badge-${t.status}">${t.status}</span>
						${t.direction === 'outbound' ? `<span class="badge badge-outbound">sent</span>` : ''}
						${t.replied ? `<span class="badge badge-replied">&#8617; Replied</span>` : ''}
						${t.forwarded ? `<span class="badge badge-forwarded">&#8618; Forwarded</span>` : ''}
						${t.is_starred ? `<span class="star">&#9733;</span>` : ""}
					</div>
				</div>
				<div class="detail-body">${this.renderBodyMarkdown(t)}</div>
				<div class="thread-actions">
					<button data-action="reply">&#8617; Reply</button>
					<button data-action="reply-all">&#8617;&#8617; Reply All</button>
					<button data-action="forward">&#8618; Forward</button>
				</div>
				${composeHtml}
				<div class="comments-section">
					<div class="comments-title">&#128172; Internal Comments (${comments.length})</div>
					${comments.map((cm: any) => `
						<div class="comment">
							<div class="comment-top">
								<span class="comment-author">${cm.username || this.displayName(cm.author_id) || "Anonymous"}</span>
								<span class="comment-time">${this.timeAgo(cm.created_at)}</span>
							</div>
							<div class="comment-body">${this.renderCommentBody(cm)}</div>
						</div>
					`).join("")}
					${comments.length === 0 ? `<div style="font-size:0.8rem;color:var(--rs-text-muted);padding:0.75rem">No comments yet &mdash; discuss this thread with your team before replying.</div>` : ""}
					<div class="comment-input">
						<div id="comment-editor-mount" class="comment-input-editor"></div>
						<div class="comment-input-actions">
							<button class="btn-comment" data-action="submit-comment">Add Comment</button>
						</div>
					</div>
				</div>
			</div>
		`;
	}

	private renderInlineCompose(): string {
		const t = this.currentThread;
		let defaultTo = '';
		let defaultSubject = '';
		let defaultBody = '';
		const modeLabel = this.composeMode === 'reply' ? 'Reply' :
			this.composeMode === 'reply-all' ? 'Reply All' :
			this.composeMode === 'forward' ? 'Forward' : 'New Email';

		if (t) {
			if (this.composeMode === 'reply') {
				defaultTo = t.from_address || '';
				defaultSubject = t.subject?.startsWith('Re:') ? t.subject : `Re: ${t.subject || ''}`;
				const date = new Date(t.received_at).toLocaleString();
				const from = t.from_name ? `${t.from_name} <${t.from_address}>` : (t.from_address || '');
				defaultBody = `\n\nOn ${date}, ${from} wrote:\n` +
					(t.body_text || '').split('\n').map((l: string) => `> ${l}`).join('\n');
			} else if (this.composeMode === 'reply-all') {
				const allAddrs = [t.from_address, ...(t.to_addresses || [])].filter(Boolean);
				defaultTo = allAddrs.join(', ');
				defaultSubject = t.subject?.startsWith('Re:') ? t.subject : `Re: ${t.subject || ''}`;
				const date = new Date(t.received_at).toLocaleString();
				const from = t.from_name ? `${t.from_name} <${t.from_address}>` : (t.from_address || '');
				defaultBody = `\n\nOn ${date}, ${from} wrote:\n` +
					(t.body_text || '').split('\n').map((l: string) => `> ${l}`).join('\n');
			} else if (this.composeMode === 'forward') {
				defaultSubject = t.subject?.startsWith('Fwd:') ? t.subject : `Fwd: ${t.subject || ''}`;
				const date = new Date(t.received_at).toLocaleString();
				defaultBody = '\n\n---------- Forwarded message ----------\n' +
					`From: ${t.from_name || ''} <${t.from_address || ''}>\n` +
					`Date: ${date}\n` +
					`Subject: ${t.subject || ''}\n` +
					`To: ${(t.to_addresses || []).join(', ')}\n\n` +
					(t.body_text || '');
			}
		}

		return `
			<div class="compose-panel">
				<h3>&#9998; ${modeLabel}</h3>
				<div class="compose-field">
					<label>To</label>
					<input id="compose-to" type="text" placeholder="recipient@example.com" value="${this.escapeHtml(defaultTo)}" />
				</div>
				<div class="compose-field">
					<label>Subject</label>
					<input id="compose-subject" type="text" value="${this.escapeHtml(defaultSubject)}" />
				</div>
				<div class="compose-field" style="margin-top:0.5rem">
					<label>Body <span style="font-weight:400;color:var(--rs-text-muted)">(Markdown supported)</span></label>
					${this.getComposeToolbarHtml()}
					<div id="compose-editor-mount" style="background:var(--rs-input-bg);border:1px solid var(--rs-input-border);border-radius:8px;padding:0.5rem 0.75rem;min-height:120px;transition:border-color 0.15s;" data-default-body="${this.escapeHtml(defaultBody)}"></div>
				</div>
				<div class="compose-threshold">
					&#128274; This email will be submitted for multi-sig approval before sending.
				</div>
				<div class="compose-actions">
					<button class="btn-submit" data-action="submit-reply">Submit for Approval</button>
					<button class="btn-cancel" data-action="cancel-compose">Cancel</button>
				</div>
			</div>
		`;
	}

	/** Render thread body as markdown HTML (read-only). */
	private renderBodyMarkdown(t: any): string {
		if (t.body_html) return t.body_html;
		if (!t.body_text) return '(no content)';
		try {
			return marked.parse(t.body_text, { async: false }) as string;
		} catch {
			return `<pre style="white-space:pre-wrap">${this.escapeHtml(t.body_text)}</pre>`;
		}
	}

	/** Render a comment body — supports tiptap-json, markdown, or plain text. */
	private renderCommentBody(cm: any): string {
		if (cm.content_format === 'tiptap-json') {
			try {
				const md = tiptapToMarkdown(cm.body);
				return marked.parse(md, { async: false }) as string;
			} catch { /* fallback */ }
		}
		// Treat plain text as markdown
		try {
			return marked.parse(cm.body || '', { async: false }) as string;
		} catch {
			return this.escapeHtml(cm.body || '');
		}
	}

	/** Compose toolbar HTML (bold, italic, underline, strike, link, code, lists). */
	private getComposeToolbarHtml(): string {
		return `
			<div class="compose-toolbar" id="compose-toolbar">
				<button type="button" data-cmd="bold" title="Bold (Ctrl+B)"><strong>B</strong></button>
				<button type="button" data-cmd="italic" title="Italic (Ctrl+I)"><em>I</em></button>
				<button type="button" data-cmd="underline" title="Underline (Ctrl+U)"><u>U</u></button>
				<button type="button" data-cmd="strike" title="Strikethrough"><s>S</s></button>
				<div class="toolbar-sep"></div>
				<button type="button" data-cmd="bulletList" title="Bullet list">&bull;</button>
				<button type="button" data-cmd="orderedList" title="Numbered list">1.</button>
				<button type="button" data-cmd="taskList" title="Task list">&#9745;</button>
				<div class="toolbar-sep"></div>
				<button type="button" data-cmd="code" title="Inline code">&lt;/&gt;</button>
				<button type="button" data-cmd="codeBlock" title="Code block">&#9000;</button>
				<button type="button" data-cmd="blockquote" title="Quote">&#8220;</button>
				<button type="button" data-cmd="link" title="Insert link">&#128279;</button>
			</div>
		`;
	}

	/** Mount a TipTap compose editor with optional Yjs collaboration. */
	private mountComposeEditor(defaultBody: string, roomSuffix: string) {
		const container = this.shadow.getElementById('compose-editor-mount');
		if (!container) return;

		this.destroyComposeEditor();

		const runtime = (window as any).__rspaceOfflineRuntime;
		const spaceSlug = runtime?.space || this.space;

		// Create Y.Doc for collaborative editing
		this.composeYdoc = new Y.Doc();
		const fragment = this.composeYdoc.getXmlFragment('content');
		const roomName = `rinbox:${spaceSlug}:compose:${roomSuffix}`;

		// IndexedDB persistence for offline drafts
		this.composeYIndexedDb = new IndexeddbPersistence(roomName, this.composeYdoc);

		// Connect Yjs provider if runtime available
		if (runtime?.isInitialized) {
			this.composeYjsProvider = new RSpaceYjsProvider(roomName, this.composeYdoc, runtime);
			const username = getUsername() || 'Anonymous';
			this.composeYjsProvider.awareness.setLocalStateField('user', {
				name: username,
				color: this.userColor(username),
			});
		}

		// Migrate default body into Yjs if fragment is empty
		this.composeYIndexedDb.on('synced', () => {
			if (fragment.length === 0 && defaultBody.trim() && this.composeEditor) {
				this.composeEditor.commands.setContent(defaultBody, { emitUpdate: false });
			}
		});

		this.composeEditor = new Editor({
			element: container,
			editable: true,
			extensions: [
				StarterKit.configure({
					heading: { levels: [1, 2, 3] },
					undoRedo: false,
					link: false,
					underline: false,
				}),
				Link.configure({ openOnClick: false }),
				TaskList, TaskItem.configure({ nested: true }),
				Placeholder.configure({ placeholder: 'Write your message... (Markdown supported)' }),
				Underline,
				Markdown.configure({ html: true, transformPastedText: true, transformCopiedText: true }),
			],
			onSelectionUpdate: () => this.updateComposeToolbarState(),
		});

		// Register Yjs plugins
		this.composeEditor.registerPlugin(ySyncPlugin(fragment));
		this.composeEditor.registerPlugin(yUndoPlugin());
		if (this.composeYjsProvider) {
			this.composeEditor.registerPlugin(
				yCursorPlugin(this.composeYjsProvider.awareness, {
					cursorBuilder: this.buildCollabCursor.bind(this),
				})
			);
		}

		// Wire toolbar
		this.wireComposeToolbar();
	}

	/** Mount lightweight TipTap editor for comments (no Yjs). */
	private mountCommentEditor() {
		const container = this.shadow.getElementById('comment-editor-mount');
		if (!container) return;

		this.destroyCommentEditor();

		this.commentEditor = new Editor({
			element: container,
			editable: true,
			extensions: [
				StarterKit.configure({
					heading: { levels: [1, 2, 3] },
					link: false,
					underline: false,
				}),
				Link.configure({ openOnClick: false }),
				Placeholder.configure({ placeholder: 'Add a comment...' }),
				Underline,
				Markdown.configure({ html: true, transformPastedText: true, transformCopiedText: true }),
			],
		});
	}

	/** Wire compose toolbar button clicks to editor commands. */
	private wireComposeToolbar() {
		const toolbar = this.shadow.getElementById('compose-toolbar');
		if (!toolbar || !this.composeEditor) return;
		toolbar.querySelectorAll('button[data-cmd]').forEach(btn => {
			btn.addEventListener('click', (e) => {
				e.preventDefault();
				const cmd = (btn as HTMLElement).dataset.cmd!;
				const editor = this.composeEditor;
				if (!editor) return;
				switch (cmd) {
					case 'bold': editor.chain().focus().toggleBold().run(); break;
					case 'italic': editor.chain().focus().toggleItalic().run(); break;
					case 'underline': editor.chain().focus().toggleUnderline().run(); break;
					case 'strike': editor.chain().focus().toggleStrike().run(); break;
					case 'bulletList': editor.chain().focus().toggleBulletList().run(); break;
					case 'orderedList': editor.chain().focus().toggleOrderedList().run(); break;
					case 'taskList': editor.chain().focus().toggleTaskList().run(); break;
					case 'code': editor.chain().focus().toggleCode().run(); break;
					case 'codeBlock': editor.chain().focus().toggleCodeBlock().run(); break;
					case 'blockquote': editor.chain().focus().toggleBlockquote().run(); break;
					case 'link': {
						const url = prompt('Link URL:');
						if (url) editor.chain().focus().setLink({ href: url }).run();
						break;
					}
				}
				this.updateComposeToolbarState();
			});
		});
	}

	/** Update active state on compose toolbar buttons. */
	private updateComposeToolbarState() {
		const toolbar = this.shadow.getElementById('compose-toolbar');
		if (!toolbar || !this.composeEditor) return;
		const editor = this.composeEditor;
		toolbar.querySelectorAll('button[data-cmd]').forEach(btn => {
			const cmd = (btn as HTMLElement).dataset.cmd!;
			let active = false;
			try {
				switch (cmd) {
					case 'bold': active = editor.isActive('bold'); break;
					case 'italic': active = editor.isActive('italic'); break;
					case 'underline': active = editor.isActive('underline'); break;
					case 'strike': active = editor.isActive('strike'); break;
					case 'bulletList': active = editor.isActive('bulletList'); break;
					case 'orderedList': active = editor.isActive('orderedList'); break;
					case 'taskList': active = editor.isActive('taskList'); break;
					case 'code': active = editor.isActive('code'); break;
					case 'codeBlock': active = editor.isActive('codeBlock'); break;
					case 'blockquote': active = editor.isActive('blockquote'); break;
					case 'link': active = editor.isActive('link'); break;
				}
			} catch { /* ignore */ }
			btn.classList.toggle('active', active);
		});
	}

	/** Build collab cursor element for Yjs awareness. */
	private buildCollabCursor(user: { name: string; color: string }) {
		const cursor = document.createElement('span');
		cursor.className = 'collab-cursor';
		cursor.style.borderLeftColor = user.color;
		const label = document.createElement('span');
		label.className = 'collab-cursor-label';
		label.style.backgroundColor = user.color;
		label.textContent = user.name;
		cursor.appendChild(label);
		return cursor;
	}

	/** Deterministic color from string. */
	private userColor(id: string): string {
		let hash = 0;
		for (let i = 0; i < id.length; i++) {
			hash = ((hash << 5) - hash + id.charCodeAt(i)) | 0;
		}
		return `hsl(${Math.abs(hash) % 360}, 70%, 50%)`;
	}

	/** Destroy compose editor and Yjs resources. */
	private destroyComposeEditor() {
		this.composeEditor?.destroy();
		this.composeEditor = null;
		this.composeYjsProvider?.destroy();
		this.composeYjsProvider = null;
		this.composeYIndexedDb?.destroy();
		this.composeYIndexedDb = null;
		if (this.composeYdoc) { this.composeYdoc.destroy(); this.composeYdoc = null; }
	}

	/** Destroy comment editor. */
	private destroyCommentEditor() {
		this.commentEditor?.destroy();
		this.commentEditor = null;
	}

	/** Destroy all editors. */
	private destroyEditors() {
		this.destroyComposeEditor();
		this.destroyCommentEditor();
	}

	private escapeHtml(s: string): string {
		return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
	}

	private renderApprovals(): string {
		const composeForm = this.composeOpen && this.view === 'approvals' ? `
			<div class="compose-panel">
				<h3>&#9998; Draft for Multi-Sig Approval</h3>
				<div class="compose-field">
					<label>To</label>
					<input id="compose-to" type="email" placeholder="recipient@example.com" />
				</div>
				<div class="compose-field">
					<label>Subject</label>
					<input id="compose-subject" type="text" placeholder="Email subject" />
				</div>
				<div class="compose-field" style="margin-top:0.5rem">
					<label>Body <span style="font-weight:400;color:var(--rs-text-muted)">(Markdown supported)</span></label>
					${this.getComposeToolbarHtml()}
					<div id="compose-editor-mount" style="background:var(--rs-input-bg);border:1px solid var(--rs-input-border);border-radius:8px;padding:0.5rem 0.75rem;min-height:120px;transition:border-color 0.15s;" data-default-body=""></div>
				</div>
				<div class="compose-threshold">
					&#128274; This email requires <strong style="color:#818cf8">multi-sig</strong> team member signatures before it will be sent.
				</div>
				<div class="compose-actions">
					<button class="btn-submit" data-action="submit-compose">Submit for Approval</button>
					<button class="btn-cancel" data-action="cancel-compose">Cancel</button>
				</div>
			</div>
		` : "";

		if (this.approvals.length === 0 && !this.composeOpen) {
			return `
				<button class="btn-compose" data-action="compose">&#9998; Compose for Approval</button>
				<div class="empty">
					<p style="font-size:2rem;margin-bottom:1rem">&#9989;</p>
					<p style="font-size:1rem;color:var(--rs-text-primary);font-weight:500;margin-bottom:0.5rem">No pending approvals</p>
					<p style="font-size:0.8rem;color:var(--rs-text-muted);max-width:380px;margin:0 auto 1.5rem;line-height:1.5">
						When a team member drafts an outgoing email, it appears here for multi-sig approval.
						Collect the required signatures before it sends.
					</p>
				</div>`;
		}

		const cards = this.approvals.map((a) => {
			const pct = a.required_signatures > 0 ? Math.min(100, Math.round((a.signature_count / a.required_signatures) * 100)) : 0;
			const statusClass = a.status.toLowerCase();
			const isAgentDraft = a.author_id?.startsWith('agent:');
			const signers = (a.signers || []).map((s: any) => {
				const initials = (s.name || "?").split(" ").map((w: string) => w[0]).join("").slice(0, 2).toUpperCase();
				const isSigned = s.vote === "APPROVE";
				return `
					<div class="signer-row">
						<div class="signer-avatar ${isSigned ? "signed" : "waiting"}">${initials}</div>
						<span class="signer-name ${isSigned ? "signed" : "waiting"}">${s.name}</span>
						<span class="signer-status ${isSigned ? "signed" : "waiting"}">
							${isSigned ? "&#10003; Signed " + this.timeAgo(s.signed_at) : "Awaiting signature"}
						</span>
					</div>`;
			}).join("");

			const replyBadge = a.reply_type && a.reply_type !== 'new'
				? `<span class="badge badge-comments" style="margin-left:0.5rem">${a.reply_type}</span>`
				: '';

			return `
			<div class="approval-card status-${statusClass}">
				<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:0.25rem">
					<div style="font-weight:600;font-size:0.95rem;flex:1;margin-right:0.5rem;color:var(--rs-text-primary)">
						${a.subject}
						${replyBadge}
						${isAgentDraft ? '<span class="badge badge-bot">bot draft</span>' : ''}
					</div>
					<span class="badge badge-${statusClass}">${a.status}</span>
				</div>
				<div class="approval-to">To: ${(a.to_addresses || []).map((e: string) => `<code>${e}</code>`).join(", ")}</div>
				<div style="font-size:0.75rem;color:var(--rs-text-muted);margin-bottom:0.35rem">${this.timeAgo(a.created_at)}</div>
				${a.body_text ? `<div class="approval-body-preview">${this.snippet(a.body_text, 200)}</div>` : ""}
				<div class="approval-progress">
					<div class="progress-bar"><div class="progress-fill ${statusClass}" style="width:${pct}%"></div></div>
					<span class="progress-text">${a.signature_count || 0} / ${a.required_signatures} signatures</span>
				</div>
				${signers ? `<div class="signer-list">${signers}</div>` : ""}
				${a.status === "PENDING" ? `
					<div class="approval-actions">
						<button class="btn-approve" data-approve="${a.id}">&#10003; Approve &amp; Sign</button>
						<button class="btn-reject" data-reject="${a.id}">&#10007; Reject</button>
					</div>
				` : ""}
			</div>`;
		}).join("");

		return `
			<button class="btn-compose" data-action="compose">&#9998; Compose for Approval</button>
			${composeForm}
			${cards}
		`;
	}

	private renderPersonal(): string {
		const connectForm = this.connectFormOpen ? `
			<div class="compose-panel">
				<h3>&#128233; Connect Email Account</h3>
				<div class="compose-field">
					<label>Label</label>
					<input id="pi-label" type="text" placeholder="Gmail, Work, etc." />
				</div>
				<div class="compose-field">
					<label>Email Address</label>
					<input id="pi-email" type="email" placeholder="you@example.com" />
				</div>
				<div class="compose-field">
					<label>IMAP Host</label>
					<input id="pi-imap-host" type="text" placeholder="imap.gmail.com" />
				</div>
				<div class="compose-field">
					<label>IMAP Port</label>
					<input id="pi-imap-port" type="number" value="993" />
				</div>
				<div class="compose-field">
					<label>IMAP Username</label>
					<input id="pi-imap-user" type="text" placeholder="you@example.com" />
				</div>
				<div class="compose-field">
					<label>IMAP Password</label>
					<input id="pi-imap-pass" type="password" placeholder="App password" />
				</div>
				<div class="compose-field">
					<label>SMTP Host (optional, defaults to IMAP host)</label>
					<input id="pi-smtp-host" type="text" placeholder="smtp.gmail.com" />
				</div>
				<div class="compose-field">
					<label>SMTP Port</label>
					<input id="pi-smtp-port" type="number" value="587" />
				</div>
				<div class="compose-actions">
					<button class="btn-submit" data-action="submit-connect">Connect &amp; Test</button>
					<button class="btn-cancel" data-action="cancel-connect">Cancel</button>
				</div>
			</div>
		` : '';

		const cards = this.personalInboxes.map((pi: any) => `
			<div class="inbox-card">
				<div style="font-size:1.5rem">&#128233;</div>
				<div class="inbox-card-info">
					<div class="inbox-card-label">${pi.label}</div>
					<div class="inbox-card-email">${pi.email}</div>
					<div class="inbox-card-status ${pi.status}">${pi.status}${pi.last_sync_at ? ` — synced ${this.timeAgo(pi.last_sync_at)}` : ''}</div>
				</div>
				<div class="inbox-card-actions">
					<button data-disconnect="${pi.id}">Disconnect</button>
				</div>
			</div>
		`).join('');

		return `
			<button class="btn-compose" data-action="connect-account">&#128233; Connect Account</button>
			${connectForm}
			${cards}
			${!this.connectFormOpen && this.personalInboxes.length === 0 ? `
				<div class="empty">
					<p style="font-size:2rem;margin-bottom:1rem">&#128233;</p>
					<p style="font-size:1rem;color:var(--rs-text-primary);font-weight:500;margin-bottom:0.5rem">No personal inboxes</p>
					<p style="font-size:0.8rem;color:var(--rs-text-muted);max-width:380px;margin:0 auto;line-height:1.5">
						Connect your personal email accounts (Gmail, Outlook, etc.) to read and reply from rSpace.
					</p>
				</div>
			` : ''}
		`;
	}

	private renderAgents(): string {
		const agentForm = this.agentFormOpen ? `
			<div class="compose-panel">
				<h3>&#129302; Create Agent Inbox</h3>
				<div class="compose-field">
					<label>Agent Name</label>
					<input id="agent-name" type="text" placeholder="Support Bot, Triage Agent..." />
				</div>
				<div class="compose-field">
					<label>Email Address</label>
					<input id="agent-email" type="email" placeholder="support-bot@rspace.online" />
				</div>
				<div class="compose-field">
					<label>Personality / System Prompt</label>
					<textarea id="agent-personality" placeholder="You are a helpful support agent. Be concise and friendly..."></textarea>
				</div>
				<div style="display:flex;gap:1rem;margin-bottom:0.75rem">
					<label style="display:flex;align-items:center;gap:0.5rem;font-size:0.8rem;color:var(--rs-text-secondary);cursor:pointer">
						<input type="checkbox" id="agent-auto-reply" /> Auto-reply
					</label>
					<label style="display:flex;align-items:center;gap:0.5rem;font-size:0.8rem;color:var(--rs-text-secondary);cursor:pointer">
						<input type="checkbox" id="agent-auto-classify" checked /> Auto-classify
					</label>
				</div>
				<div class="compose-actions">
					<button class="btn-submit" data-action="submit-agent">Create Agent</button>
					<button class="btn-cancel" data-action="cancel-agent">Cancel</button>
				</div>
			</div>
		` : '';

		const cards = this.agentInboxes.map((ai: any) => `
			<div class="inbox-card">
				<div style="font-size:1.5rem">&#129302;</div>
				<div class="inbox-card-info">
					<div class="inbox-card-label">${ai.name}</div>
					<div class="inbox-card-email">${ai.email}</div>
					<div class="agent-toggle">
						Auto-reply: <span class="${ai.auto_reply ? 'on' : 'off'}">${ai.auto_reply ? 'ON' : 'OFF'}</span>
						&middot; Auto-classify: <span class="${ai.auto_classify ? 'on' : 'off'}">${ai.auto_classify ? 'ON' : 'OFF'}</span>
						&middot; ${ai.rules?.length || 0} rule${(ai.rules?.length || 0) !== 1 ? 's' : ''}
					</div>
				</div>
				<div class="inbox-card-actions">
					<button data-delete-agent="${ai.id}">Delete</button>
				</div>
			</div>
		`).join('');

		return `
			<button class="btn-compose" data-action="create-agent">&#129302; Create Agent</button>
			${agentForm}
			${cards}
			${!this.agentFormOpen && this.agentInboxes.length === 0 ? `
				<div class="empty">
					<p style="font-size:2rem;margin-bottom:1rem">&#129302;</p>
					<p style="font-size:1rem;color:var(--rs-text-primary);font-weight:500;margin-bottom:0.5rem">No agent inboxes</p>
					<p style="font-size:0.8rem;color:var(--rs-text-muted);max-width:380px;margin:0 auto;line-height:1.5">
						Create AI-managed email agents that auto-classify, tag, and draft replies based on rules.
						Agent-drafted replies still go through the approval workflow.
					</p>
				</div>
			` : ''}
		`;
	}

	private renderHelp(): string {
		return `
			<div class="help-overlay" data-action="close-help-overlay">
				<div class="help-panel" data-help-panel>
					<button class="help-close" data-action="close-help">&times;</button>
					<div class="help-title">&#128232; rInbox Guide</div>
					<div class="help-subtitle">
						Collaborative email with multi-sig approval workflows.
						Shared mailboxes for your team where outgoing emails require collective sign-off.
					</div>

					<div class="help-grid">
						<div class="help-card">
							<div class="help-card-icon">&#128232;</div>
							<h4>Shared Mailboxes</h4>
							<p>Create team mailboxes that multiple members can read, triage, and respond to with role-based access.</p>
						</div>
						<div class="help-card">
							<div class="help-card-icon">&#128172;</div>
							<h4>Threaded Comments</h4>
							<p>Discuss emails internally before replying. @mention teammates and coordinate responses.</p>
						</div>
						<div class="help-card">
							<div class="help-card-icon">&#9989;</div>
							<h4>Multi-Sig Approval</h4>
							<p>Outgoing emails require M-of-N signatures. Board votes and treasury approvals built into email.</p>
						</div>
						<div class="help-card">
							<div class="help-card-icon">&#128260;</div>
							<h4>IMAP Sync</h4>
							<p>Connect any IMAP mailbox. Server-side polling syncs emails automatically every 30 seconds.</p>
						</div>
					</div>

					<div class="help-section">
						<h3>How It Works</h3>
						<div class="help-steps">
							<div class="help-step">
								<span class="help-step-num">1</span>
								<span class="help-step-text"><strong>Create a mailbox</strong> &mdash; set up a shared inbox for your team with an email address and invite members.</span>
							</div>
							<div class="help-step">
								<span class="help-step-num">2</span>
								<span class="help-step-text"><strong>Triage &amp; discuss</strong> &mdash; incoming emails sync via IMAP. Team members triage threads, leave comments, and star important messages.</span>
							</div>
							<div class="help-step">
								<span class="help-step-num">3</span>
								<span class="help-step-text"><strong>Reply, forward, or compose</strong> &mdash; draft a reply inline, forward to someone, or compose a new email. All go through approval.</span>
							</div>
							<div class="help-step">
								<span class="help-step-num">4</span>
								<span class="help-step-text"><strong>Approve &amp; send</strong> &mdash; collect the required signatures. Once the threshold is met, the email sends automatically via SMTP.</span>
							</div>
						</div>
					</div>

					<div class="help-section">
						<h3>Use Cases</h3>
						<div class="help-steps">
							<div class="help-step">
								<span class="help-step-num" style="background:#22d3ee">&#9878;</span>
								<span class="help-step-text"><strong>Governance</strong> &mdash; board decisions require 3-of-5 signers before the email sends.</span>
							</div>
							<div class="help-step">
								<span class="help-step-num" style="background:#22c55e">&#128176;</span>
								<span class="help-step-text"><strong>Treasury</strong> &mdash; 2-of-3 finance team co-sign payment authorizations. Bridges email to on-chain wallets.</span>
							</div>
							<div class="help-step">
								<span class="help-step-num" style="background:#fbbf24">&#128270;</span>
								<span class="help-step-text"><strong>Audit Trails</strong> &mdash; every email is co-signed with cryptographic proof of who approved what.</span>
							</div>
						</div>
					</div>

					<div style="text-align:center;margin-top:1.5rem">
						<a href="${getModuleApiBase("rinbox")}/about" class="help-cta">View Full Feature Page &rarr;</a>
					</div>
				</div>
			</div>
		`;
	}

	private goBack() {
		const prev = this._history.back();
		if (!prev) return;
		if (this.view === "thread") this.composeOpen = false;
		if (prev.view === "mailboxes") this.currentMailbox = null;
		this.view = prev.view;
		this.render();
	}

	private bindEvents() {
		// Navigation
		this.shadow.querySelectorAll("[data-nav]").forEach((btn) => {
			btn.addEventListener("click", () => {
				const nav = (btn as HTMLElement).dataset.nav as any;
				if (nav === "approvals") {
					if (this.view !== "approvals") { this._history.push(this.view); this._history.push("approvals"); }
					this.view = "approvals";
					this.loadApprovals();
				} else if (nav === "mailboxes") {
					if (this.view !== "mailboxes") { this._history.push(this.view); this._history.push("mailboxes"); }
					this.view = "mailboxes";
					this.currentMailbox = null;
					this.render();
				} else if (nav === "threads") {
					if (this.view !== "threads") { this._history.push(this.view); this._history.push("threads"); }
					this.view = "threads";
					this.render();
				} else if (nav === "personal") {
					if (this.view !== "personal") { this._history.push(this.view); this._history.push("personal"); }
					this.view = "personal";
					this.loadPersonalInboxes();
				} else if (nav === "agents") {
					if (this.view !== "agents") { this._history.push(this.view); this._history.push("agents"); }
					this.view = "agents";
					this.loadAgentInboxes();
				}
			});
		});

		// Back
		const backBtn = this.shadow.querySelector("[data-action='back']");
		if (backBtn) {
			backBtn.addEventListener("click", () => this.goBack());
		}

		// Help
		this.shadow.querySelectorAll("[data-action='help']").forEach((btn) => {
			btn.addEventListener("click", () => { this.helpOpen = true; this.render(); });
		});
		const helpOverlay = this.shadow.querySelector("[data-action='close-help-overlay']");
		if (helpOverlay) {
			helpOverlay.addEventListener("click", (e) => {
				if ((e.target as HTMLElement).dataset.action === "close-help-overlay") {
					this.helpOpen = false; this.render();
				}
			});
		}
		const helpClose = this.shadow.querySelector("[data-action='close-help']");
		if (helpClose) {
			helpClose.addEventListener("click", () => { this.helpOpen = false; this.render(); });
		}

		// Forwarding trigger + modal actions
		this.shadow.querySelector("[data-action='fwd-open']")?.addEventListener("click", () => {
			this._fwdModalOpen = true; this.render();
		});
		this.shadow.querySelector("[data-action='fwd-enable']")?.addEventListener("click", () => this._enableForwarding());
		this.shadow.querySelector("[data-action='fwd-disable']")?.addEventListener("click", () => this._disableForwarding());
		this.shadow.querySelector("[data-action='fwd-save-enable']")?.addEventListener("click", () => this._saveEmailAndEnable());
		this.shadow.querySelectorAll("[data-action='fwd-close']").forEach(btn => {
			btn.addEventListener("click", () => { this._fwdModalOpen = false; this.render(); });
		});
		const fwdOverlay = this.shadow.querySelector("[data-action='fwd-close-overlay']");
		if (fwdOverlay) {
			fwdOverlay.addEventListener("click", (e) => {
				if ((e.target as HTMLElement).dataset.action === "fwd-close-overlay") {
					this._fwdModalOpen = false; this.render();
				}
			});
		}

		// Mailbox click
		this.shadow.querySelectorAll("[data-mailbox]").forEach((card) => {
			card.addEventListener("click", () => {
				const slug = (card as HTMLElement).dataset.mailbox!;
				this._history.push(this.view);
				this._history.push("threads", { mailbox: slug });
				this.currentMailbox = this.mailboxes.find((m) => m.slug === slug);
				this.view = "threads";
				this.loadThreads(slug);
			});
		});

		// Thread click
		this.shadow.querySelectorAll("[data-thread]").forEach((row) => {
			row.addEventListener("click", () => {
				const id = (row as HTMLElement).dataset.thread!;
				this._history.push(this.view);
				this._history.push("thread", { threadId: id });
				this.view = "thread";
				this.composeOpen = false;
				this.loadThread(id);
			});
		});

		// Filter
		this.shadow.querySelectorAll("[data-filter]").forEach((btn) => {
			btn.addEventListener("click", () => {
				this.filter = (btn as HTMLElement).dataset.filter as any;
				if (this.currentMailbox) this.loadThreads(this.currentMailbox.slug);
			});
		});

		// Thread actions: Reply / Reply All / Forward
		this.shadow.querySelector("[data-action='reply']")?.addEventListener("click", () => {
			this.composeMode = 'reply';
			this.composeOpen = true;
			this.render();
		});
		this.shadow.querySelector("[data-action='reply-all']")?.addEventListener("click", () => {
			this.composeMode = 'reply-all';
			this.composeOpen = true;
			this.render();
		});
		this.shadow.querySelector("[data-action='forward']")?.addEventListener("click", () => {
			this.composeMode = 'forward';
			this.composeOpen = true;
			this.render();
		});

		// Submit reply/forward from thread detail
		this.shadow.querySelector("[data-action='submit-reply']")?.addEventListener("click", async () => {
			if (this.space === "demo" || this.showingSampleData) {
				alert("Reply is disabled in demo mode.");
				return;
			}
			const t = this.currentThread;
			if (!t) return;

			const to = (this.shadow.getElementById("compose-to") as HTMLInputElement)?.value.trim();
			const subject = (this.shadow.getElementById("compose-subject") as HTMLInputElement)?.value.trim();
			// Extract body from TipTap editor as markdown
			const body = this.composeEditor
				? tiptapToMarkdown(JSON.stringify(this.composeEditor.getJSON())).trim()
				: '';
			if (!subject || !body) { alert("Please fill subject and body."); return; }

			const base = window.location.pathname.replace(/\/$/, "");
			try {
				if (this.composeMode === 'reply') {
					await fetch(`${base}/api/threads/${t.id}/reply`, {
						method: "POST",
						headers: this.getAuthHeaders(),
						body: JSON.stringify({ body_text: body }),
					});
				} else if (this.composeMode === 'reply-all') {
					await fetch(`${base}/api/threads/${t.id}/reply-all`, {
						method: "POST",
						headers: this.getAuthHeaders(),
						body: JSON.stringify({ body_text: body }),
					});
				} else if (this.composeMode === 'forward') {
					if (!to) { alert("Forward requires a To address."); return; }
					await fetch(`${base}/api/threads/${t.id}/forward`, {
						method: "POST",
						headers: this.getAuthHeaders(),
						body: JSON.stringify({ body_text: body, to_addresses: to.split(',').map(s => s.trim()).filter(Boolean) }),
					});
				}
				this.composeOpen = false;
				this.view = "approvals";
				this.loadApprovals();
			} catch { alert("Failed to submit. Please try again."); }
		});

		// Submit comment from thread detail
		this.shadow.querySelector("[data-action='submit-comment']")?.addEventListener("click", async () => {
			if (this.space === "demo" || this.showingSampleData) {
				alert("Comments are disabled in demo mode.");
				return;
			}
			const t = this.currentThread;
			if (!t || !this.commentEditor) return;
			const json = JSON.stringify(this.commentEditor.getJSON());
			const text = this.commentEditor.getText().trim();
			if (!text) { alert("Please write a comment."); return; }
			try {
				const base = window.location.pathname.replace(/\/$/, "");
				await fetch(`${base}/api/threads/${t.id}/comments`, {
					method: "POST",
					headers: this.getAuthHeaders(),
					body: JSON.stringify({ text: json, content_format: 'tiptap-json' }),
				});
				this.commentEditor.commands.clearContent();
				this.loadThread(t.id);
			} catch { alert("Failed to post comment."); }
		});

		// Compose (from approvals view)
		const composeBtn = this.shadow.querySelector("[data-action='compose']");
		if (composeBtn) {
			composeBtn.addEventListener("click", () => {
				this.composeMode = 'new';
				this.composeOpen = !this.composeOpen;
				this.render();
			});
		}
		const cancelCompose = this.shadow.querySelector("[data-action='cancel-compose']");
		if (cancelCompose) {
			cancelCompose.addEventListener("click", () => { this.composeOpen = false; this.render(); });
		}
		const submitCompose = this.shadow.querySelector("[data-action='submit-compose']");
		if (submitCompose) {
			submitCompose.addEventListener("click", async () => {
				if (this.space === "demo" || this.showingSampleData) {
					alert("Compose is disabled in demo mode. In a live space, this would submit the email for team approval.");
					return;
				}
				const to = (this.shadow.getElementById("compose-to") as HTMLInputElement)?.value.trim();
				const subject = (this.shadow.getElementById("compose-subject") as HTMLInputElement)?.value.trim();
				// Extract body from TipTap editor as markdown
				const body = this.composeEditor
					? tiptapToMarkdown(JSON.stringify(this.composeEditor.getJSON())).trim()
					: '';
				if (!to || !subject || !body) { alert("Please fill all fields."); return; }
				try {
					const base = window.location.pathname.replace(/\/$/, "");
					const mailbox = this.currentMailbox?.slug || "";
					await fetch(`${base}/api/approvals`, {
						method: "POST",
						headers: this.getAuthHeaders(),
						body: JSON.stringify({ to_addresses: [to], subject, body_text: body, mailbox_slug: mailbox }),
					});
					this.composeOpen = false;
					this.loadApprovals();
				} catch { alert("Failed to submit. Please try again."); }
			});
		}

		// Approval actions
		this.shadow.querySelectorAll("[data-approve]").forEach((btn) => {
			btn.addEventListener("click", async () => {
				if (this.space === "demo" || this.showingSampleData) { alert("Approvals are disabled in demo mode."); return; }
				const id = (btn as HTMLElement).dataset.approve!;
				const base = window.location.pathname.replace(/\/$/, "");
				await fetch(`${base}/api/approvals/${id}/sign`, {
					method: "POST",
					headers: this.getAuthHeaders(),
					body: JSON.stringify({ vote: "APPROVE" }),
				});
				this.loadApprovals();
			});
		});
		this.shadow.querySelectorAll("[data-reject]").forEach((btn) => {
			btn.addEventListener("click", async () => {
				if (this.space === "demo" || this.showingSampleData) { alert("Approvals are disabled in demo mode."); return; }
				const id = (btn as HTMLElement).dataset.reject!;
				const base = window.location.pathname.replace(/\/$/, "");
				await fetch(`${base}/api/approvals/${id}/sign`, {
					method: "POST",
					headers: this.getAuthHeaders(),
					body: JSON.stringify({ vote: "REJECT" }),
				});
				this.loadApprovals();
			});
		});

		// Personal inbox actions
		this.shadow.querySelector("[data-action='connect-account']")?.addEventListener("click", () => {
			this.connectFormOpen = !this.connectFormOpen;
			this.render();
		});
		this.shadow.querySelector("[data-action='cancel-connect']")?.addEventListener("click", () => {
			this.connectFormOpen = false;
			this.render();
		});
		this.shadow.querySelector("[data-action='submit-connect']")?.addEventListener("click", async () => {
			if (this.space === "demo" || this.showingSampleData) { alert("Connect is disabled in demo mode."); return; }
			const label = (this.shadow.getElementById("pi-label") as HTMLInputElement)?.value.trim();
			const email = (this.shadow.getElementById("pi-email") as HTMLInputElement)?.value.trim();
			const imapHost = (this.shadow.getElementById("pi-imap-host") as HTMLInputElement)?.value.trim();
			const imapPort = parseInt((this.shadow.getElementById("pi-imap-port") as HTMLInputElement)?.value || "993");
			const imapUser = (this.shadow.getElementById("pi-imap-user") as HTMLInputElement)?.value.trim();
			const imapPass = (this.shadow.getElementById("pi-imap-pass") as HTMLInputElement)?.value;
			const smtpHost = (this.shadow.getElementById("pi-smtp-host") as HTMLInputElement)?.value.trim();
			const smtpPort = parseInt((this.shadow.getElementById("pi-smtp-port") as HTMLInputElement)?.value || "587");
			if (!label || !email || !imapHost || !imapUser || !imapPass) { alert("Please fill required fields."); return; }
			try {
				const base = window.location.pathname.replace(/\/$/, "");
				const resp = await fetch(`${base}/api/personal-inboxes?space=${this.space}`, {
					method: "POST",
					headers: this.getAuthHeaders(),
					body: JSON.stringify({ label, email, imap_host: imapHost, imap_port: imapPort, imap_user: imapUser, imap_pass: imapPass, smtp_host: smtpHost || undefined, smtp_port: smtpPort }),
				});
				if (!resp.ok) {
					const err = await resp.json();
					alert(err.error || "Failed to connect.");
					return;
				}
				this.connectFormOpen = false;
				this.loadPersonalInboxes();
			} catch { alert("Failed to connect. Please try again."); }
		});
		this.shadow.querySelectorAll("[data-disconnect]").forEach((btn) => {
			btn.addEventListener("click", async () => {
				if (this.space === "demo" || this.showingSampleData) { alert("Demo mode."); return; }
				const id = (btn as HTMLElement).dataset.disconnect!;
				const base = window.location.pathname.replace(/\/$/, "");
				await fetch(`${base}/api/personal-inboxes/${id}`, { method: "DELETE", headers: this.getAuthHeaders() });
				this.loadPersonalInboxes();
			});
		});

		// Agent inbox actions
		this.shadow.querySelector("[data-action='create-agent']")?.addEventListener("click", () => {
			this.agentFormOpen = !this.agentFormOpen;
			this.render();
		});
		this.shadow.querySelector("[data-action='cancel-agent']")?.addEventListener("click", () => {
			this.agentFormOpen = false;
			this.render();
		});
		this.shadow.querySelector("[data-action='submit-agent']")?.addEventListener("click", async () => {
			if (this.space === "demo" || this.showingSampleData) { alert("Agent creation is disabled in demo mode."); return; }
			const name = (this.shadow.getElementById("agent-name") as HTMLInputElement)?.value.trim();
			const email = (this.shadow.getElementById("agent-email") as HTMLInputElement)?.value.trim();
			const personality = (this.shadow.getElementById("agent-personality") as HTMLTextAreaElement)?.value.trim();
			const autoReply = (this.shadow.getElementById("agent-auto-reply") as HTMLInputElement)?.checked;
			const autoClassify = (this.shadow.getElementById("agent-auto-classify") as HTMLInputElement)?.checked;
			if (!name || !email) { alert("Name and email required."); return; }
			try {
				const base = window.location.pathname.replace(/\/$/, "");
				await fetch(`${base}/api/agent-inboxes?space=${this.space}`, {
					method: "POST",
					headers: this.getAuthHeaders(),
					body: JSON.stringify({ name, email, personality, auto_reply: autoReply, auto_classify: autoClassify, rules: [] }),
				});
				this.agentFormOpen = false;
				this.loadAgentInboxes();
			} catch { alert("Failed to create agent."); }
		});
		this.shadow.querySelectorAll("[data-delete-agent]").forEach((btn) => {
			btn.addEventListener("click", async () => {
				if (this.space === "demo" || this.showingSampleData) { alert("Demo mode."); return; }
				const id = (btn as HTMLElement).dataset.deleteAgent!;
				const base = window.location.pathname.replace(/\/$/, "");
				await fetch(`${base}/api/agent-inboxes/${id}`, { method: "DELETE", headers: this.getAuthHeaders() });
				this.loadAgentInboxes();
			});
		});

		// ── Mount TipTap editors after DOM is ready ──
		this.mountEditorsAfterRender();
	}

	/** Mount compose and comment editors if their containers exist in DOM. */
	private mountEditorsAfterRender() {
		// Compose editor (thread reply or approval compose)
		const composeMountEl = this.shadow.getElementById('compose-editor-mount');
		if (composeMountEl) {
			const defaultBody = composeMountEl.dataset.defaultBody || '';
			const threadId = this.currentThread?.id || 'new';
			const suffix = this.view === 'approvals' ? `approval:${threadId}` : `thread:${threadId}:${this.composeMode}`;
			this.mountComposeEditor(defaultBody, suffix);
		}

		// Comment editor (thread detail view)
		const commentMountEl = this.shadow.getElementById('comment-editor-mount');
		if (commentMountEl) {
			this.mountCommentEditor();
		}
	}
}

customElements.define("folk-inbox-client", FolkInboxClient);
