(function () {
    window.AgentFrontendModules = window.AgentFrontendModules || {};

    window.AgentFrontendModules.createAppRuntimeModule = function createAppRuntimeModule(scope) {
        return (function () {
            with (scope) {
    async function _runCopilotById(copilotId, source = '') {
        const selectedId = String(copilotId || '').trim();
        const state = _collectCopilotStateFromUi();
        const quickstart = COPILOT_QUICKSTARTS.find((item) => item.id === selectedId);
        const statusTarget = selectedId === 'follow-up' ? 'followup' : 'morning';
        if (!quickstart) {
            _setAllCopilotStatuses('This Co-Pilot quickstart is not available yet.', 'err');
            return;
        }
        if (!appSettings.main_model) {
            _setAllCopilotStatuses('Select a main model in Settings before starting a Co-Pilot.', 'err');
            return;
        }
        currentSessionId = null;
        _setChatSessionPill('');
        _appendChatBubble('system', quickstart.announce);
        if (!state.copilot_enabled) {
            const sourceLabel = source === 'footer' ? 'Quickstart' : 'Run now';
            _setCopilotStatus(statusTarget, `${sourceLabel} started ${quickstart.label} once. Daily Co-Pilot scheduling remains off until you enable it in Settings.`, 'ok');
        } else {
            _setCopilotStatus(statusTarget, `Running ${quickstart.label} now.`, 'ok');
        }
        const goal = selectedId === 'follow-up'
            ? _buildCopilotFollowupGoal()
            : _buildCopilotMorningBriefGoal();
        sendStart(goal, appSettings.main_model || '', '');
    }

    async function _runSelectedCopilotQuickstart(source = '') {
        const selectedId = String(copilotQuickstartSelect ? copilotQuickstartSelect.value : '').trim();
        if (!selectedId) {
            _setAllCopilotStatuses('Choose a Co-Pilot quickstart first.', 'err');
            return;
        }
        await _runCopilotById(selectedId, source);
    }

    function _generateScheduleId() {
        return 'sch_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 6);
    }

    function _intervalMs(interval, unit) {
        const n = Math.max(1, Number(interval) || 1);
        switch (unit) {
            case 'minutes': return n * 60 * 1000;
            case 'hours':   return n * 60 * 60 * 1000;
            case 'days':    return n * 24 * 60 * 60 * 1000;
            case 'weeks':   return n * 7 * 24 * 60 * 60 * 1000;
            case 'months':  return n * 30 * 24 * 60 * 60 * 1000;
            default:        return n * 60 * 1000;
        }
    }

    function _formatInterval(interval, unit) {
        const n = Number(interval) || 1;
        const labels = { minutes: 'min', hours: 'hr', days: 'day', weeks: 'week', months: 'month' };
        const label = labels[unit] || unit;
        return `${n} ${label}${n !== 1 ? 's' : ''}`;
    }

    const SCHEDULES_STORAGE_KEY = 'agent_schedules';

    function _loadSchedules() {
        try { return JSON.parse(localStorage.getItem(SCHEDULES_STORAGE_KEY)) || []; }
        catch { return []; }
    }

    function _saveSchedules(schedules) {
        localStorage.setItem(SCHEDULES_STORAGE_KEY, JSON.stringify(schedules));
    }

    function addSchedule(goal, model, context, interval, unit, enabled = true, firstRun = '') {
        const unitMap = { minutes: 'm', hours: 'h', days: 'd', weeks: 'w', months: 'mo' };
        const intervalStr = `${Number(interval) || 1}${unitMap[unit] || 'm'}`;
        // Build request body, include first_run if set
        const reqBody = { goal, interval: intervalStr };
        if (firstRun) reqBody.first_run = new Date(firstRun).toISOString();
        // Create server-side schedule (backend ticker handles recurring execution)
        authFetch(`${BACKEND_URL}/api/schedules`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(reqBody),
        })
            .then(r => r.json())
            .then(data => {
                if (data.ok) {
                    renderSchedules();
                    _appendChatBubble('system', `Schedule created — "${goal}" will run every ${_formatInterval(interval, unit)}.`);
                } else {
                    _appendChatBubble('system', `⚠ Failed to create schedule: ${data.error || 'Unknown error'}`);
                }
            })
            .catch(() => {
                // Server unreachable — fall back to local schedule
                const schedules = _loadSchedules();
                const id = _generateScheduleId();
                const now = Date.now();
                const ms = _intervalMs(interval, unit);
                schedules.push({
                    id, goal, model, context: context || '',
                    interval: Number(interval) || 1, unit, enabled,
                    createdAt: now, nextRun: now + ms, lastRun: null, runCount: 0,
                });
                _saveSchedules(schedules);
                _startSchedulePolling();
                renderSchedules();
                _appendChatBubble('system', `Schedule created (local) — "${goal}" will run every ${_formatInterval(interval, unit)}. ⚠ Requires browser to stay open.`);
            });
    }

    function removeSchedule(id) {
        let schedules = _loadSchedules();
        schedules = schedules.filter(s => s.id !== id);
        _saveSchedules(schedules);
        // Stop polling if no enabled schedules remain
        if (!schedules.some(s => s.enabled)) _stopSchedulePolling();
        renderSchedules();
    }

    function toggleSchedule(id, enabled) {
        const schedules = _loadSchedules();
        const idx = schedules.findIndex(s => s.id === id);
        if (idx === -1) return;
        schedules[idx].enabled = enabled;
        if (enabled) {
            schedules[idx].nextRun = Date.now() + _intervalMs(schedules[idx].interval, schedules[idx].unit);
            _startSchedulePolling();
        }
        _saveSchedules(schedules);
        // Stop polling if no enabled schedules remain
        if (!schedules.some(s => s.enabled)) _stopSchedulePolling();
        renderSchedules();
    }

    function _executeScheduledRun(schedule) {
        const schedules = _loadSchedules();
        const idx = schedules.findIndex(s => s.id === schedule.id);
        if (idx === -1 || !schedules[idx].enabled) return;

        // Check WebSocket connection — reconnect if needed
        if (!ws || ws.readyState !== WebSocket.OPEN) {
            console.warn('[Scheduler] WebSocket not connected, attempting reconnect before scheduled run…');
            if (_reconnectTimer) { clearTimeout(_reconnectTimer); _reconnectTimer = null; }
            connectWebSocket();
            // Retry after a short delay to let WS connect
            setTimeout(() => {
                if (ws && ws.readyState === WebSocket.OPEN) {
                    _executeScheduledRun(schedule); // retry
                } else {
                    console.error('[Scheduler] WebSocket still not connected — skipping run for:', schedule.goal);
                    _appendChatBubble('system', `⚠ Scheduled run skipped (no server connection): "${schedule.goal}"`);
                    // Re-arm to try again at next interval
                    const now = Date.now();
                    schedules[idx].nextRun = now + _intervalMs(schedule.interval, schedule.unit);
                    _saveSchedules(schedules);
                    renderSchedules();
                }
            }, 2000);
            return;
        }

        // Guard: don't run if we're already at max concurrent runs
        if (_activeRunCount() >= 3) {
            console.warn('[Scheduler] Max concurrent runs reached — deferring:', schedule.goal);
            _appendChatBubble('system', `⏳ Scheduled run deferred (3 runs active): "${schedule.goal}"`);
            // Retry in 30 seconds
            const retryDelay = 30000;
            const fresh = _loadSchedules();
            const fi = fresh.findIndex(s => s.id === schedule.id);
            if (fi !== -1) {
                fresh[fi].nextRun = Date.now() + retryDelay;
                _saveSchedules(fresh);
            }
            renderSchedules();
            return;
        }

        // Update run stats
        const now = Date.now();
        schedules[idx].lastRun = now;
        schedules[idx].runCount += 1;
        schedules[idx].nextRun = now + _intervalMs(schedule.interval, schedule.unit);
        _saveSchedules(schedules);

        // Execute the agent run
        _appendChatBubble('system', `🔄 Scheduled run: "${schedule.goal}" (run #${schedules[idx].runCount})`);
        currentSessionId = null;
        _setChatSessionPill('');
        sendStart(schedule.goal, schedule.model || appSettings.main_model || '', schedule.context || '');

        renderSchedules();
    }

    // ── Robust polling loop ──────────────────────────────────────────────
    // Instead of relying solely on setTimeout (which browsers throttle in
    // inactive tabs and lose after sleep), we run a lightweight setInterval
    // every 15 seconds that checks all enabled schedules.
    const SCHEDULE_POLL_INTERVAL = 15_000; // 15s
    let _schedulePollId = null;

    function _schedulePollingTick() {
        const schedules = _loadSchedules();
        const now = Date.now();
        let anyFired = false;
        for (const s of schedules) {
            if (!s.enabled) continue;
            if (s.nextRun && now >= s.nextRun) {
                // This schedule is due — execute it
                console.log('[Scheduler] Polling tick firing:', s.goal, 'was due', Math.round((now - s.nextRun) / 1000), 's ago');
                _executeScheduledRun(s);
                anyFired = true;
                break; // One at a time to avoid flooding
            }
        }
        // Update the countdown display in the Schedules tab if visible
        if (!anyFired && schedulesContainer && schedulesContainer.style.display !== 'none') {
            _updateScheduleCountdowns();
        }
    }

    function _updateScheduleCountdowns() {
        // Live-update the "next:" text in schedule card metas
        const cards = schedulesContainer.querySelectorAll('.schedule-card-meta');
        const schedules = _loadSchedules();
        cards.forEach((metaEl, i) => {
            if (i >= schedules.length) return;
            const s = schedules[i];
            if (!s.enabled || !s.nextRun) return;
            const parts = [`Every ${_formatInterval(s.interval, s.unit)}`];
            if (s.runCount > 0) parts.push(`${s.runCount} run${s.runCount !== 1 ? 's' : ''}`);
            if (s.lastRun) parts.push(`last: ${new Date(s.lastRun).toLocaleString()}`);
            const remaining = Math.max(0, s.nextRun - Date.now());
            if (remaining < 60000) parts.push('next: <1 min');
            else if (remaining < 3600000) parts.push(`next: ${Math.ceil(remaining / 60000)} min`);
            else parts.push(`next: ${new Date(s.nextRun).toLocaleTimeString()}`);
            metaEl.textContent = parts.join(' · ');
        });
    }

    function _startSchedulePolling() {
        if (_schedulePollId) return; // already running
        _schedulePollId = setInterval(_schedulePollingTick, SCHEDULE_POLL_INTERVAL);
        // Also do one immediate check (catches missed runs after page reload / wake from sleep)
        setTimeout(_schedulePollingTick, 1500);
    }

    function _stopSchedulePolling() {
        if (_schedulePollId) {
            clearInterval(_schedulePollId);
            _schedulePollId = null;
        }
    }

    function _initScheduleTimers() {
        const schedules = _loadSchedules();
        if (schedules.some(s => s.enabled)) {
            _startSchedulePolling();
        }
    }

    function _formatIntervalSeconds(seconds) {
        if (!seconds || seconds <= 0) return '??';
        if (seconds >= 2592000 && seconds % 2592000 === 0) { const n = seconds / 2592000; return `${n} month${n !== 1 ? 's' : ''}`; }
        if (seconds >= 604800 && seconds % 604800 === 0) { const n = seconds / 604800; return `${n} week${n !== 1 ? 's' : ''}`; }
        if (seconds >= 86400 && seconds % 86400 === 0) { const n = seconds / 86400; return `${n} day${n !== 1 ? 's' : ''}`; }
        if (seconds >= 3600 && seconds % 3600 === 0) { const n = seconds / 3600; return `${n} hr${n !== 1 ? 's' : ''}`; }
        const n = Math.round(seconds / 60); return `${n} min${n !== 1 ? 's' : ''}`;
    }

    function renderSchedules() {
        if (!schedulesContainer) return;
        const localSchedules = _loadSchedules();

        // Fetch server-side schedules too
        authFetch(`${BACKEND_URL}/api/schedules`)
            .then(r => r.json())
            .then(data => {
                const serverSchedules = Array.isArray(data.schedules) ? data.schedules : [];
                _updateCopilotStatusFromSchedules(serverSchedules, localSchedules);
                _renderScheduleCards(localSchedules, serverSchedules);
            })
            .catch(() => {
                // If server unreachable, just show local schedules
                _updateCopilotStatusFromSchedules([], localSchedules);
                _renderScheduleCards(localSchedules, []);
            });
    }

    /** Convert interval seconds to {amount, unitChar} for the edit form. */
    function _secondsToIntervalParts(seconds) {
        if (!seconds || seconds <= 0) return { amount: 1, unit: 'minutes' };
        if (seconds >= 2592000 && seconds % 2592000 === 0) return { amount: seconds / 2592000, unit: 'months' };
        if (seconds >= 604800 && seconds % 604800 === 0) return { amount: seconds / 604800, unit: 'weeks' };
        if (seconds >= 86400 && seconds % 86400 === 0) return { amount: seconds / 86400, unit: 'days' };
        if (seconds >= 3600 && seconds % 3600 === 0) return { amount: seconds / 3600, unit: 'hours' };
        return { amount: Math.max(1, Math.round(seconds / 60)), unit: 'minutes' };
    }

    /** Build the inline edit form that replaces a card's content. */
    function _buildEditForm(card, { goal, interval, unit, nextRun, onSave, onCancel }) {
        card.innerHTML = '';
        card.classList.add('schedule-card-editing');

        const form = document.createElement('div');
        form.className = 'schedule-edit-form';

        const goalLabel = document.createElement('label');
        goalLabel.className = 'schedule-edit-label';
        goalLabel.textContent = 'Goal';
        const goalInput = document.createElement('textarea');
        goalInput.className = 'schedule-edit-input';
        goalInput.rows = 2;
        goalInput.value = goal || '';

        const intervalRow = document.createElement('div');
        intervalRow.className = 'schedule-edit-interval-row';
        const intervalLabel = document.createElement('label');
        intervalLabel.className = 'schedule-edit-label';
        intervalLabel.textContent = 'Every';
        const intervalInput = document.createElement('input');
        intervalInput.type = 'number';
        intervalInput.className = 'schedule-interval-input';
        intervalInput.min = '1';
        intervalInput.value = interval || 1;
        const unitSelect = document.createElement('select');
        unitSelect.className = 'schedule-unit-select';
        ['minutes', 'hours', 'days', 'weeks', 'months'].forEach(u => {
            const opt = document.createElement('option');
            opt.value = u; opt.textContent = u;
            if (u === unit) opt.selected = true;
            unitSelect.appendChild(opt);
        });
        intervalRow.appendChild(intervalLabel);
        intervalRow.appendChild(intervalInput);
        intervalRow.appendChild(unitSelect);

        const nextRunRow = document.createElement('div');
        nextRunRow.className = 'schedule-first-run-row';
        const nextRunLabel = document.createElement('label');
        nextRunLabel.className = 'schedule-first-run-label';
        nextRunLabel.textContent = 'Next run';
        const nextRunInput = document.createElement('input');
        nextRunInput.type = 'datetime-local';
        nextRunInput.className = 'schedule-first-run-input';
        if (nextRun) {
            // Convert ISO string to datetime-local format (YYYY-MM-DDTHH:MM)
            const dt = new Date(nextRun);
            const pad = n => String(n).padStart(2, '0');
            nextRunInput.value = `${dt.getFullYear()}-${pad(dt.getMonth()+1)}-${pad(dt.getDate())}T${pad(dt.getHours())}:${pad(dt.getMinutes())}`;
        }
        nextRunRow.appendChild(nextRunLabel);
        nextRunRow.appendChild(nextRunInput);

        const btns = document.createElement('div');
        btns.className = 'schedule-edit-actions';
        const saveBtn = document.createElement('button');
        saveBtn.className = 'btn-xs primary';
        saveBtn.textContent = 'Save';
        saveBtn.addEventListener('click', () => {
            onSave(goalInput.value.trim(), Number(intervalInput.value) || 1, unitSelect.value, nextRunInput.value);
        });
        const cancelBtn = document.createElement('button');
        cancelBtn.className = 'btn-xs';
        cancelBtn.textContent = 'Cancel';
        cancelBtn.addEventListener('click', onCancel);
        btns.appendChild(saveBtn);
        btns.appendChild(cancelBtn);

        form.appendChild(goalLabel);
        form.appendChild(goalInput);
        form.appendChild(intervalRow);
        form.appendChild(nextRunRow);
        form.appendChild(btns);
        card.appendChild(form);

        goalInput.focus();
    }

    function _renderScheduleCards(localSchedules, serverSchedules) {
        schedulesContainer.innerHTML = '';

        if (localSchedules.length === 0 && serverSchedules.length === 0) {
            schedulesContainer.innerHTML = '<div class="schedules-empty"><svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>No schedules yet<br><span style="font-size:.75rem;opacity:.7">Set &ldquo;Repeat&rdquo; when starting a flow, or use the Schedule button in History.</span></div>';
            return;
        }

        // Render server-side schedules first
        serverSchedules.forEach(s => {
            const card = document.createElement('div');
            card.className = 'schedule-card' + (s.enabled !== false ? '' : ' disabled');
            const displayGoal = String(s.label || s.goal || '(no goal)');

            const header = document.createElement('div');
            header.className = 'schedule-card-header';

            const goal = document.createElement('div');
            goal.className = 'schedule-card-goal';
            goal.textContent = displayGoal;

            header.appendChild(goal);

            const toggle = document.createElement('label');
            toggle.className = 'schedule-toggle';
            const cb = document.createElement('input');
            cb.type = 'checkbox';
            cb.checked = s.enabled !== false;
            cb.addEventListener('change', () => {
                authFetch(`${BACKEND_URL}/api/schedules/${s.id}`, {
                    method: 'PATCH',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ enabled: cb.checked }),
                })
                    .then(r => r.json())
                    .then(() => renderSchedules())
                    .catch(() => renderSchedules());
            });
            const slider = document.createElement('span');
            slider.className = 'schedule-toggle-slider';
            toggle.appendChild(cb);
            toggle.appendChild(slider);
            header.appendChild(toggle);

            const meta = document.createElement('div');
            meta.className = 'schedule-card-meta';
            const parts = [`Every ${s.interval_label || _formatIntervalSeconds(s.interval_seconds)}`];
            if (s.next_run) {
                const nextDate = new Date(s.next_run);
                const remaining = Math.max(0, nextDate.getTime() - Date.now());
                if (remaining < 60000) parts.push('next: <1 min');
                else if (remaining < 3600000) parts.push(`next: ${Math.ceil(remaining / 60000)} min`);
                else parts.push(`next: ${nextDate.toLocaleTimeString()}`);
            }
            if (s.created_at) parts.push(`created: ${new Date(s.created_at).toLocaleDateString()}`);
            meta.textContent = parts.join(' · ');

            const actions = document.createElement('div');
            actions.className = 'schedule-card-actions';

            const editBtn = document.createElement('button');
            editBtn.className = 'btn-xs';
            editBtn.textContent = 'Edit';
            editBtn.addEventListener('click', () => {
                const { amount, unit } = _secondsToIntervalParts(s.interval_seconds);
                _buildEditForm(card, {
                    goal: s.goal,
                    interval: amount,
                    unit: unit,
                    nextRun: s.next_run || '',
                    onSave: (newGoal, newInterval, newUnit, newNextRun) => {
                        const unitMap = { minutes: 'm', hours: 'h', days: 'd', weeks: 'w', months: 'mo' };
                        const intervalStr = `${newInterval}${unitMap[newUnit] || 'm'}`;
                        const body = {};
                        if (newGoal && newGoal !== s.goal) body.goal = newGoal;
                        body.interval = intervalStr;
                        if (newNextRun) body.next_run = new Date(newNextRun).toISOString();
                        authFetch(`${BACKEND_URL}/api/schedules/${s.id}`, {
                            method: 'PATCH',
                            headers: { 'Content-Type': 'application/json' },
                            body: JSON.stringify(body),
                        })
                            .then(r => r.json())
                            .then(res => { if (!res.ok) alert(res.error || 'Update failed'); renderSchedules(); })
                            .catch(() => renderSchedules());
                    },
                    onCancel: () => renderSchedules(),
                });
            });

            const delBtn = document.createElement('button');
            delBtn.className = 'btn-xs danger';
            delBtn.textContent = 'Delete';
            delBtn.addEventListener('click', () => {
                if (!confirm(`Delete server schedule "${displayGoal}"?`)) return;
                authFetch(`${BACKEND_URL}/api/schedules/${s.id}`, { method: 'DELETE' })
                    .then(r => r.json())
                    .then(() => renderSchedules())
                    .catch(() => renderSchedules());
            });

            const runNowBtn = document.createElement('button');
            runNowBtn.className = 'btn-xs primary';
            runNowBtn.textContent = 'Run Now';
            runNowBtn.addEventListener('click', () => {
                _appendChatBubble('system', `Manual trigger: "${displayGoal}"`);
                currentSessionId = null;
                _setChatSessionPill('');
                sendStart(s.goal, appSettings.main_model || '', '');
            });

            actions.appendChild(editBtn);
            actions.appendChild(runNowBtn);
            actions.appendChild(delBtn);

            card.appendChild(header);
            card.appendChild(meta);
            card.appendChild(actions);
            schedulesContainer.appendChild(card);
        });

        // Render local (client-side) schedules
        localSchedules.forEach(s => {
            const card = document.createElement('div');
            card.className = 'schedule-card' + (s.enabled ? '' : ' disabled');
            const displayGoal = String(s.label || s.goal || '(no goal)');

            const header = document.createElement('div');
            header.className = 'schedule-card-header';

            const goal = document.createElement('div');
            goal.className = 'schedule-card-goal';
            goal.textContent = displayGoal;

            const toggle = document.createElement('label');
            toggle.className = 'schedule-toggle';
            const cb = document.createElement('input');
            cb.type = 'checkbox';
            cb.checked = s.enabled;
            cb.addEventListener('change', () => toggleSchedule(s.id, cb.checked));
            const slider = document.createElement('span');
            slider.className = 'schedule-toggle-slider';
            toggle.appendChild(cb);
            toggle.appendChild(slider);

            header.appendChild(goal);
            header.appendChild(toggle);

            const meta = document.createElement('div');
            meta.className = 'schedule-card-meta';
            const parts = [`Every ${_formatInterval(s.interval, s.unit)}`];
            if (s.runCount > 0) parts.push(`${s.runCount} run${s.runCount !== 1 ? 's' : ''}`);
            if (s.lastRun) parts.push(`last: ${new Date(s.lastRun).toLocaleString()}`);
            if (s.enabled && s.nextRun) {
                const remaining = Math.max(0, s.nextRun - Date.now());
                if (remaining < 60000) parts.push('next: <1 min');
                else if (remaining < 3600000) parts.push(`next: ${Math.ceil(remaining / 60000)} min`);
                else parts.push(`next: ${new Date(s.nextRun).toLocaleTimeString()}`);
            }
            meta.textContent = parts.join(' · ');

            const actions = document.createElement('div');
            actions.className = 'schedule-card-actions';

            const editBtn = document.createElement('button');
            editBtn.className = 'btn-xs';
            editBtn.textContent = 'Edit';
            editBtn.addEventListener('click', () => {
                _buildEditForm(card, {
                    goal: s.goal,
                    interval: s.interval,
                    unit: s.unit,
                    onSave: (newGoal, newInterval, newUnit) => {
                        const schedules = _loadSchedules();
                        const idx = schedules.findIndex(x => x.id === s.id);
                        if (idx === -1) { renderSchedules(); return; }
                        schedules[idx].goal = newGoal || schedules[idx].goal;
                        schedules[idx].interval = newInterval;
                        schedules[idx].unit = newUnit;
                        if (schedules[idx].enabled) {
                            schedules[idx].nextRun = Date.now() + _intervalMs(newInterval, newUnit);
                        }
                        _saveSchedules(schedules);
                        renderSchedules();
                    },
                    onCancel: () => renderSchedules(),
                });
            });

            const runNowBtn = document.createElement('button');
            runNowBtn.className = 'btn-xs primary';
            runNowBtn.textContent = 'Run Now';
            runNowBtn.addEventListener('click', () => {
                _appendChatBubble('system', `Manual trigger: "${displayGoal}"`);
                currentSessionId = null;
                _setChatSessionPill('');
                sendStart(s.goal, s.model || appSettings.main_model || '', s.context || '');
            });

            const delBtn = document.createElement('button');
            delBtn.className = 'btn-xs danger';
            delBtn.textContent = 'Delete';
            delBtn.addEventListener('click', () => {
                if (confirm(`Delete schedule "${displayGoal}"?`)) removeSchedule(s.id);
            });

            actions.appendChild(editBtn);
            actions.appendChild(runNowBtn);
            actions.appendChild(delBtn);

            card.appendChild(header);
            card.appendChild(meta);
            card.appendChild(actions);
            schedulesContainer.appendChild(card);
        });
    }

    // --- Schedule popover for history cards ---
    function _showSchedulePopover(anchorBtn, goal, model, context) {
        // Remove existing popover if any
        const existing = document.querySelector('.schedule-popover');
        if (existing) existing.remove();

        const pop = document.createElement('div');
        pop.className = 'schedule-popover';
        pop.innerHTML = `
            <div class="schedule-popover-title">Schedule recurring run</div>
            <div class="schedule-popover-row">
                <span>Every</span>
                <input type="number" class="schedule-interval-input" min="1" value="30" id="pop-sched-interval">
                <select class="schedule-unit-select" id="pop-sched-unit">
                    <option value="minutes">minutes</option>
                    <option value="hours">hours</option>
                    <option value="days">days</option>
                    <option value="weeks">weeks</option>
                    <option value="months">months</option>
                </select>
            </div>
            <div class="schedule-first-run-row">
                <label class="schedule-first-run-label" for="pop-sched-first-run">First run</label>
                <input type="datetime-local" class="schedule-first-run-input" id="pop-sched-first-run">
            </div>
            <div class="schedule-popover-actions">
                <button class="btn-xs primary" id="pop-sched-save">Create</button>
                <button class="btn-xs" id="pop-sched-cancel">Cancel</button>
            </div>
        `;

        // Position popover near the anchor button
        document.body.appendChild(pop);
        const rect = anchorBtn.getBoundingClientRect();
        pop.style.position = 'fixed';
        pop.style.top = (rect.bottom + 4) + 'px';
        pop.style.left = Math.max(4, rect.left - 60) + 'px';
        pop.style.zIndex = '10000';

        const cancelBtn = pop.querySelector('#pop-sched-cancel');
        const saveBtn = pop.querySelector('#pop-sched-save');

        cancelBtn.addEventListener('click', () => pop.remove());
        saveBtn.addEventListener('click', () => {
            const interval = Number(pop.querySelector('#pop-sched-interval').value) || 30;
            const unit = pop.querySelector('#pop-sched-unit').value;
            const firstRun = pop.querySelector('#pop-sched-first-run').value || '';
            addSchedule(goal, model, context, interval, unit, true, firstRun);
            pop.remove();
        });

        // Close on outside click
        setTimeout(() => {
            function onOutsideClick(e) {
                if (!pop.contains(e.target) && e.target !== anchorBtn) {
                    pop.remove();
                    document.removeEventListener('mousedown', onOutsideClick);
                }
            }
            document.addEventListener('mousedown', onOutsideClick);
        }, 50);
    }

    // --- Schedule picker in composer (dropdown style) ---
    let _schedulePickerMode = 'once'; // 'once' | 'repeat'
    const schedulePickerBtn = document.getElementById('schedule-picker-btn');
    const schedulePickerLabel = document.getElementById('schedule-picker-label');
    const schedulePickerDropdown = document.getElementById('schedule-picker-dropdown');
    const scheduleIntervalRow = document.getElementById('schedule-interval-row');
    const scheduleFirstRunRow = document.getElementById('schedule-first-run-row');
    const scheduleFirstRunInput = document.getElementById('schedule-first-run');
    const scheduleIntervalInput = document.getElementById('schedule-interval');
    const scheduleUnit = document.getElementById('schedule-unit');
    const schedCheckOnce = document.getElementById('sched-check-once');
    const schedCheckRepeat = document.getElementById('sched-check-repeat');

    function _updateSchedulePickerUI() {
        const isRepeat = _schedulePickerMode === 'repeat';
        if (schedCheckOnce) schedCheckOnce.textContent = isRepeat ? '' : '\u2713';
        if (schedCheckRepeat) schedCheckRepeat.textContent = isRepeat ? '\u2713' : '';
        if (scheduleIntervalRow) scheduleIntervalRow.style.display = isRepeat ? 'flex' : 'none';
        if (scheduleFirstRunRow) scheduleFirstRunRow.style.display = isRepeat ? 'flex' : 'none';
        if (schedulePickerBtn) schedulePickerBtn.classList.toggle('active', isRepeat);
        if (schedulePickerLabel) {
            if (isRepeat) {
                const val = scheduleIntervalInput ? scheduleIntervalInput.value : '30';
                const unitShort = { minutes: 'min', hours: 'hr', days: 'day', weeks: 'wk', months: 'mo' };
                const u = scheduleUnit ? (unitShort[scheduleUnit.value] || scheduleUnit.value) : 'min';
                schedulePickerLabel.textContent = `Every ${val} ${u}`;
            } else {
                schedulePickerLabel.textContent = 'Once';
            }
        }
        if (startBtnLabel) startBtnLabel.textContent = isRepeat ? 'Start Scheduled Flow' : 'Start Agent Flow';
    }

    function _toggleScheduleDropdown(show) {
        if (!schedulePickerDropdown) return;
        const visible = show !== undefined ? show : schedulePickerDropdown.style.display === 'none';
        schedulePickerDropdown.style.display = visible ? 'block' : 'none';
    }

    if (schedulePickerBtn) {
        schedulePickerBtn.addEventListener('click', (e) => {
            e.stopPropagation();
            _toggleScheduleDropdown();
        });
    }

    // Dropdown option clicks
    if (schedulePickerDropdown) {
        schedulePickerDropdown.querySelectorAll('.schedule-dropdown-option').forEach(opt => {
            opt.addEventListener('click', () => {
                _schedulePickerMode = opt.dataset.value;
                _updateSchedulePickerUI();
                if (_schedulePickerMode === 'once') {
                    _toggleScheduleDropdown(false);
                }
            });
        });

        // Update label live when interval/unit changes
        if (scheduleIntervalInput) {
            scheduleIntervalInput.addEventListener('input', _updateSchedulePickerUI);
        }
        if (scheduleUnit) {
            scheduleUnit.addEventListener('change', _updateSchedulePickerUI);
        }
    }

    // Close dropdown on outside click
    document.addEventListener('click', (e) => {
        if (schedulePickerDropdown && schedulePickerDropdown.style.display !== 'none') {
            const picker = document.getElementById('schedule-picker');
            if (picker && !picker.contains(e.target)) {
                _toggleScheduleDropdown(false);
            }
        }
    });

    // ── Email inline reader state ──────────────────────────────────────────
    let _pendingEmailReaderRow = null;  // the <tr> that was clicked
    let _emailReaderModal = null;       // the modal overlay element
    let _followupEmailRow = null;       // row saved for re-opening from follow-up chat
    let _draftReplyPending = false;     // true while waiting for LLM draft in email popup
    let _draftReplyRetryCount = 0;
    let _draftReplyRequest = null;

    function _resetDraftReplyState(options = {}) {
        const clearRequest = Boolean(options.clearRequest);
        const resetRetryCount = options.resetRetryCount !== false;
        _draftReplyPending = false;
        if (resetRetryCount) _draftReplyRetryCount = 0;
        if (clearRequest) _draftReplyRequest = null;
    }

    function _draftReplyModels() {
        const candidates = [
            appSettings.fast_model || '',
            appSettings.main_model || '',
            appSettings.fallback_model || '',
        ].map((v) => String(v || '').trim()).filter(Boolean);
        const unique = [];
        for (const m of candidates) {
            if (!unique.includes(m)) unique.push(m);
        }
        return unique;
    }

    function _nextDraftReplyModel() {
        const unique = _draftReplyModels();
        if (!unique.length) return '';
        const idx = Math.min(_draftReplyRetryCount, unique.length - 1);
        return unique[idx] || unique[0];
    }

    function _maxDraftReplyRetryCount() {
        return Math.max(0, _draftReplyModels().length - 1);
    }

    const _EMAIL_DRAFT_TONE_OPTIONS = [
        { value: 'match', label: 'Match sender' },
        { value: 'warm', label: 'Warm' },
        { value: 'direct', label: 'Direct' },
        { value: 'formal', label: 'Formal' },
    ];

    const _EMAIL_DRAFT_LENGTH_OPTIONS = [
        { value: 'short', label: 'Short' },
        { value: 'medium', label: 'Medium' },
        { value: 'long', label: 'Long' },
    ];

    function _normalizeDraftReplyOption(key, value) {
        const options = key === 'tone' ? _EMAIL_DRAFT_TONE_OPTIONS : _EMAIL_DRAFT_LENGTH_OPTIONS;
        const normalized = String(value || '').trim().toLowerCase();
        return options.some(opt => opt.value === normalized) ? normalized : options[0].value;
    }

    function _ensureDraftReplyOptions() {
        if (!_draftReplyRequest) return;
        _draftReplyRequest.tone = _normalizeDraftReplyOption('tone', _draftReplyRequest.tone || 'match');
        _draftReplyRequest.length = _normalizeDraftReplyOption('length', _draftReplyRequest.length || 'medium');
    }

    function _draftReplyToolbarHint(mode) {
        if (mode === 'loading') return 'Generating a draft with the selected tone and length.';
        if (mode === 'error') return 'Adjust the settings and try again from this panel.';
        return 'Change tone or length, then click Rewrite to generate a noticeably different version.';
    }

    function _buildDraftReplyOptionGroup(title, key, options) {
        const group = document.createElement('div');
        group.className = 'email-draft-option-group';

        const label = document.createElement('div');
        label.className = 'email-draft-option-label';
        label.textContent = title;
        group.appendChild(label);

        const row = document.createElement('div');
        row.className = 'email-draft-option-row';

        for (const option of options) {
            const btn = document.createElement('button');
            btn.type = 'button';
            btn.className = 'email-draft-option-btn';
            btn.dataset.draftOption = key;
            btn.dataset.value = option.value;
            btn.textContent = option.label;
            btn.addEventListener('click', () => {
                if (!_draftReplyRequest) return;
                _draftReplyRequest[key] = option.value;
                const canvas = _emailReaderModal?.querySelector('.email-draft-canvas');
                if (!canvas) return;
                canvas.querySelectorAll(`.email-draft-option-btn[data-draft-option="${key}"]`).forEach(el => {
                    el.classList.toggle('is-selected', el.dataset.value === option.value);
                });
                const hint = canvas.querySelector('.email-draft-toolbar-hint');
                if (hint && !_draftReplyPending) {
                    hint.textContent = 'Settings updated. Click Rewrite to apply them to a new draft.';
                }
            });
            row.appendChild(btn);
        }

        group.appendChild(row);
        return group;
    }

    function _syncDraftReplyOptionButtons(root) {
        if (!_draftReplyRequest || !root) return;
        _ensureDraftReplyOptions();
        root.querySelectorAll('.email-draft-option-btn').forEach(btn => {
            const key = btn.dataset.draftOption;
            btn.classList.toggle('is-selected', btn.dataset.value === _draftReplyRequest[key]);
        });
    }

    function _renderDraftReplyCanvas(mode, payload = {}) {
        if (!_emailReaderModal || !_draftReplyRequest) return {};
        _ensureDraftReplyOptions();

        const body = _emailReaderModal.querySelector('.email-reader-modal-body');
        const box = _emailReaderModal.querySelector('.email-reader-modal-box');
        const footer = box && box.querySelector('.email-reader-modal-footer');
        if (!body) return {};

        body.innerHTML = '';

        const canvas = document.createElement('div');
        canvas.className = 'email-draft-canvas';

        const toolbar = document.createElement('div');
        toolbar.className = 'email-draft-toolbar';

        const toolbarHeader = document.createElement('div');
        toolbarHeader.className = 'email-draft-toolbar-header';

        const toolbarTitle = document.createElement('div');
        toolbarTitle.className = 'email-draft-toolbar-title';
        toolbarTitle.textContent = 'Draft Controls';

        const toolbarHint = document.createElement('div');
        toolbarHint.className = 'email-draft-toolbar-hint';
        toolbarHint.textContent = _draftReplyToolbarHint(mode);

        toolbarHeader.appendChild(toolbarTitle);
        toolbarHeader.appendChild(toolbarHint);
        toolbar.appendChild(toolbarHeader);

        const toolbarGroups = document.createElement('div');
        toolbarGroups.className = 'email-draft-toolbar-groups';
        toolbarGroups.appendChild(_buildDraftReplyOptionGroup('Tone', 'tone', _EMAIL_DRAFT_TONE_OPTIONS));
        toolbarGroups.appendChild(_buildDraftReplyOptionGroup('Length', 'length', _EMAIL_DRAFT_LENGTH_OPTIONS));
        toolbar.appendChild(toolbarGroups);

        canvas.appendChild(toolbar);

        const content = document.createElement('div');
        content.className = 'email-draft-canvas-body';

        let textarea = null;
        if (mode === 'loading') {
            content.innerHTML = `<div class="email-draft-generating"><div class="email-draft-spinner"></div><span>Generating reply draft…</span></div>`;
            if (footer) footer.style.display = 'none';
        } else if (mode === 'error') {
            const safeErr = escapeHtml(String(payload.errorText || 'LLM not available.'));
            content.innerHTML = `
                <div class="email-draft-state email-draft-state--error">
                    <div class="email-draft-state-title">Could not generate a reply draft.</div>
                    <div class="email-draft-state-copy">${safeErr}</div>
                    <div class="email-draft-state-actions">
                        <button type="button" id="email-draft-retry-now" class="email-reader-modal-btn email-reader-modal-btn--primary">↻ Try Again</button>
                        <button type="button" id="email-draft-cancel" class="email-reader-modal-btn email-reader-modal-btn--secondary">Close</button>
                    </div>
                </div>`;
            if (footer) footer.style.display = 'none';
        } else {
            textarea = document.createElement('textarea');
            textarea.className = 'email-draft-textarea';
            textarea.value = String(payload.draftText || '').trim();
            content.appendChild(textarea);
        }

        canvas.appendChild(content);
        body.appendChild(canvas);
        _syncDraftReplyOptionButtons(canvas);

        if (mode === 'error') {
            body.querySelector('#email-draft-retry-now')?.addEventListener('click', () => _retryDraftReplyNow());
            body.querySelector('#email-draft-cancel')?.addEventListener('click', () => _closeEmailInlineReader());
        }

        return { body, box, footer, textarea };
    }

    function _renderDraftReplyFooter(textarea) {
        if (!_emailReaderModal || !textarea) return;
        const box = _emailReaderModal.querySelector('.email-reader-modal-box');
        if (!box) return;

        let footer = box.querySelector('.email-reader-modal-footer');
        if (!footer) {
            footer = document.createElement('div');
            footer.className = 'email-reader-modal-footer';
            box.appendChild(footer);
        }
        footer.innerHTML = '';
        footer.style.display = '';

        const messageId = _emailReaderModal.dataset.messageId || '';
        const provider = _emailReaderModal.dataset.provider || '';

        const cancelBtn = document.createElement('button');
        cancelBtn.className = 'email-reader-modal-btn email-reader-modal-btn--secondary';
        cancelBtn.textContent = 'Cancel';
        cancelBtn.addEventListener('click', () => _closeEmailInlineReader());

        const regenerateBtn = document.createElement('button');
        regenerateBtn.className = 'email-reader-modal-btn email-reader-modal-btn--secondary';
        regenerateBtn.innerHTML = '↻ Rewrite';
        regenerateBtn.addEventListener('click', () => _retryDraftReplyNow({ restartSequence: true }));

        const draftBtn = document.createElement('button');
        draftBtn.className = 'email-reader-modal-btn email-reader-modal-btn--secondary';
        draftBtn.innerHTML = '💾 Save Draft';
        draftBtn.addEventListener('click', async () => {
            draftBtn.disabled = true;
            draftBtn.textContent = 'Saving…';
            try {
                const res = await authFetch('/api/email-draft', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ message_id: messageId, body: textarea.value, provider }),
                });
                const data = await res.json();
                if (data.ok) {
                    _closeEmailInlineReader();
                    setStatus('Draft saved', 'success');
                } else {
                    alert('Could not save draft: ' + (data.message || 'Unknown error'));
                    draftBtn.disabled = false;
                    draftBtn.innerHTML = '💾 Save Draft';
                }
            } catch (e) {
                alert('Save failed: ' + e.message);
                draftBtn.disabled = false;
                draftBtn.innerHTML = '💾 Save Draft';
            }
        });

        const sendBtn = document.createElement('button');
        sendBtn.className = 'email-reader-modal-btn email-reader-modal-btn--primary';
        sendBtn.innerHTML = '✉️ Send';
        sendBtn.addEventListener('click', async () => {
            sendBtn.disabled = true;
            sendBtn.textContent = 'Sending…';
            try {
                const res = await authFetch('/api/email-reply', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ message_id: messageId, body: textarea.value, provider }),
                });
                const data = await res.json();
                if (data.ok) {
                    _closeEmailInlineReader();
                    setStatus('Email sent', 'success');
                } else {
                    alert('Could not send email: ' + (data.message || 'Unknown error'));
                    sendBtn.disabled = false;
                    sendBtn.innerHTML = '✉️ Send';
                }
            } catch (e) {
                alert('Send failed: ' + e.message);
                sendBtn.disabled = false;
                sendBtn.innerHTML = '✉️ Send';
            }
        });

        footer.appendChild(cancelBtn);
        footer.appendChild(regenerateBtn);
        footer.appendChild(draftBtn);
        footer.appendChild(sendBtn);
    }

    async function _sendDraftReplyRequest() {
        if (!_draftReplyRequest) return false;
        _ensureDraftReplyOptions();
        const modelToUse = _nextDraftReplyModel();
        try {
            const res = await authFetch('/api/email-propose-reply', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    message_id: _draftReplyRequest.messageId,
                    provider: _draftReplyRequest.provider,
                    model: modelToUse,
                    tone: _draftReplyRequest.tone,
                    length: _draftReplyRequest.length,
                }),
            });
            let data = {};
            try {
                data = await res.json();
            } catch (_) {}

            const errText = String(
                data.error || data.message || (!res.ok ? `HTTP ${res.status}` : 'Could not generate a reply draft.')
            ).trim();

            if (!res.ok || data.ok === false) {
                if (_isTransientDraftModelIssue(errText) && _draftReplyRetryCount < _maxDraftReplyRetryCount()) {
                    _draftReplyRetryCount += 1;
                    showToast('warning', 'Model temporarily unavailable. Retrying with another LLM model…', 4500);
                    setStatus('Retrying draft generation…', 'running', true);
                    return _sendDraftReplyRequest();
                }
                _draftReplyPending = false;
                _showDraftReplyRetryState(errText);
                setStatus('Draft generation failed', 'error');
                showToast('error', errText, 6000);
                return false;
            }

            const draftText = String(data.draft || '').trim();
            if (!draftText) {
                _draftReplyPending = false;
                _showDraftReplyRetryState('Could not generate a reply draft.');
                setStatus('Draft generation failed', 'error');
                return false;
            }

            _draftReplyPending = false;
            _draftReplyRequest.lastModel = String(data.model || modelToUse || '').trim();
            _draftReplyRequest.tone = _normalizeDraftReplyOption('tone', data.tone || _draftReplyRequest.tone);
            _draftReplyRequest.length = _normalizeDraftReplyOption('length', data.length || _draftReplyRequest.length);
            _resolveDraftReplyInPopup(draftText);
            setStatus('Draft ready', 'success');
            return true;
        } catch (e) {
            const errText = `Could not generate a reply draft: ${e.message}`;
            _draftReplyPending = false;
            _showDraftReplyRetryState(errText);
            setStatus('Draft generation failed', 'error');
            showToast('error', errText, 6000);
            return false;
        }
    }

    async function _retryDraftReplyNow(options = {}) {
        if (!_draftReplyRequest) return;
        const restartSequence = Boolean(options.restartSequence);
        _draftReplyPending = true;
        if (restartSequence) _draftReplyRetryCount = 0;
        setStatus('Generating draft…', 'running', true);
        _renderDraftReplyCanvas('loading');
        await _sendDraftReplyRequest();
    }

    function _showDraftReplyRetryState(errText = '') {
        _renderDraftReplyCanvas('error', { errorText: errText });
    }

    function _isTransientDraftModelIssue(text) {
        const value = String(text || '').trim();
        if (!value) return false;
        return /llm\s+not\s+available|model\s+not\s+available|no\s+models?\s+available|temporar(y|ily)\s+unavailable/i.test(value);
    }
    let _pendingInlineEmailRequest = null; // metadata for silent popup-driven email opens

    function _getEmailSubjectFromRow(row) {
        const table = row && row.closest('table');
        if (!table) return null;
        const ths = Array.from(table.querySelectorAll('thead th'));
        const subjectIdx = ths.findIndex(th => /^(subject|onderwerp)$/i.test(th.textContent.trim()));
        if (subjectIdx < 0) return null;
        const cells = row.querySelectorAll('td');
        const cell = cells[subjectIdx];
        return cell ? cell.textContent.trim() : null;
    }

    function _openEmailInlineReader(row) {
        if (!row || !ws || ws.readyState !== WebSocket.OPEN) return;
        const idx = row.dataset.emailIndex;
        if (!idx) return;
        const explicitMessageId = row.dataset.messageId || '';
        const explicitProvider = row.dataset.provider || '';

        // Close any existing reader first
        _closeEmailInlineReader();

        // Mark row as selected
        row.classList.add('email-row-selected');
        _pendingEmailReaderRow = row;

        // Build modal overlay
        const overlay = document.createElement('div');
        overlay.className = 'email-reader-modal';
        overlay.addEventListener('click', (e) => {
            if (e.target === overlay) _closeEmailInlineReader();
        });

        // Store idx and subject on overlay dataset for use in follow-up
        overlay.dataset.emailIdx = idx;
        if (explicitMessageId) overlay.dataset.messageId = explicitMessageId;
        if (explicitProvider) overlay.dataset.provider = explicitProvider;
        const emailSubject = _getEmailSubjectFromRow(row);
        if (emailSubject) overlay.dataset.emailSubject = emailSubject;

        const box = document.createElement('div');
        box.className = 'email-reader-modal-box';

        const header = document.createElement('div');
        header.className = 'email-reader-modal-header';
        const titleSpan = document.createElement('span');
        titleSpan.className = 'email-reader-modal-title';
        titleSpan.textContent = emailSubject ? `\ud83d\udce7 ${emailSubject}` : `\ud83d\udce7 E-mail ${idx}`;
        const closeBtn = document.createElement('button');
        closeBtn.className = 'email-reader-modal-close';
        closeBtn.title = 'Close (Esc)';
        closeBtn.textContent = '\u2715';
        closeBtn.addEventListener('click', () => _closeEmailInlineReader());
        header.appendChild(titleSpan);
        header.appendChild(closeBtn);

        const body = document.createElement('div');
        body.className = 'email-reader-modal-body';
        body.innerHTML = '<div class="email-reader-modal-loading">Loading email…</div>';

        box.appendChild(header);
        box.appendChild(body);
        overlay.appendChild(box);
        document.body.appendChild(overlay);
        _emailReaderModal = overlay;

        // ESC to close
        const _escHandler = (e) => {
            if (e.key === 'Escape') {
                _closeEmailInlineReader();
                document.removeEventListener('keydown', _escHandler);
            }
        };
        document.addEventListener('keydown', _escHandler);

        // Send silent chat message — no user bubble
        // Prefer the viewed history session so that "open email N" resolves
        // against the correct session's email list, not the latest active session.
        const viewedHistorySessionId = typeof getViewedHistorySessionId === 'function'
            ? String(getViewedHistorySessionId() || '').trim()
            : '';
        const targetSession = viewedHistorySessionId || _preferredConversationSessionId || currentSessionId;
        let targetRunId = _findRunIdBySessionId(targetSession, false);
        if ((!targetRunId || !runs.has(targetRunId)) && targetSession) {
            targetRunId = _ensureChatLane(targetSession);
        }
        targetRunId = targetRunId || _currentHandlingRunId || activeRunId || '';
        _removeThinkingBubble();
        setStatus('E-mail openen\u2026', 'running', true);
        _pendingInlineEmailRequest = {
            emailIdx: idx,
            messageId: explicitMessageId,
            provider: explicitProvider,
            requestedAt: Date.now(),
        };
        ws.send(JSON.stringify({
            action: 'chat',
            session_id: targetSession,
            run_id: targetRunId,
            model: appSettings.fast_model || appSettings.main_model || '',
            message: `open email ${idx}`,
            context: '',
            _email_inline: true,
            email_message_id: explicitMessageId,
            email_provider: explicitProvider,
        }));
    }

    function _closeEmailInlineReader() {
        if (_pendingEmailReaderRow) {
            _pendingEmailReaderRow.classList.remove('email-row-selected');
            _pendingEmailReaderRow = null;
        }
        _resetDraftReplyState({ clearRequest: true });
        if (_emailReaderModal && _emailReaderModal.parentNode) {
            _emailReaderModal.parentNode.removeChild(_emailReaderModal);
        }
        _emailReaderModal = null;
    }

    function _looksLikeStructuredEmailPayload(text) {
        const raw = String(text || '');
        if (!raw) return false;
        const hasSubject = /^Subject:\s+.+$/im.test(raw);
        const hasFrom = /^From:\s+.+$/im.test(raw);
        const hasProvider = /^Provider:\s+.+$/im.test(raw);
        const hasMessageId = /^Message-ID:\s+.+$/im.test(raw);
        return (hasSubject && hasFrom) || (hasProvider && hasMessageId);
    }

    function _consumePendingInlineEmailReply(text) {
        if (!_pendingInlineEmailRequest) return false;

        const raw = String(text || '').trim();

        if (_emailReaderModal) {
            _pendingInlineEmailRequest = null;
            return _resolveEmailInlineReply(raw);
        }

        // Once the popup is closed, the pending reply still belongs to that
        // inline email-open request and must never spill into the main chat.
        _pendingInlineEmailRequest = null;
        if (raw) {
            setStatus('Verbonden met server', 'success');
        }
        return true;
    }

    function _renderEmailView(text) {
        // Parse structured email text (Subject:/From:/To:/CC:/Date:/Attachments:/Message-ID:/Message ID:/Provider: headers + body)
        const headerFields = ['Subject', 'From', 'To', 'CC', 'Date', 'Attachments', 'Message-ID', 'Message ID', 'Provider'];
        const headerRE = new RegExp(`^(${headerFields.map(f => f.replace('-', '\\-')).join('|')}): ?(.*)$`, 'i');
        const lines = text.split('\n');
        const headers = {};
        let bodyStart = 0;
        // Collect header lines until an empty line or non-header line after the first header
        let inHeaders = false;
        for (let i = 0; i < lines.length; i++) {
            const m = lines[i].match(headerRE);
            if (m) {
                const headerName = m[1].toLowerCase().replace(/\s+/g, '-');
                headers[headerName] = m[2].trim();
                inHeaders = true;
                bodyStart = i + 1;
            } else if (inHeaders) {
                // first non-header line ends the header block
                if (!lines[i].trim()) bodyStart = i + 1; // skip blank separator
                break;
            }
        }
        const bodyText = lines.slice(bodyStart).join('\n').trim();
        const displayOrder = ['from', 'to', 'cc', 'date', 'attachments'];

        const wrap = document.createElement('div');
        wrap.className = 'email-view';

        function _sanitizeEmailHtmlForSrcdoc(html) {
            const rawHtml = String(html || '');
            const _sanitizeCssText = (cssText) => String(cssText || '')
                .replace(/\/\*[\s\S]*?\*\//g, '')
                .replace(/@import[\s\S]*?;/gi, '')
                .replace(/@font-face\s*{[\s\S]*?}/gi, '')
                .replace(/url\(\s*(['"]?)(?!data:|#)[^)]*\1\s*\)/gi, "url('')");

            try {
                const parser = new DOMParser();
                const doc = parser.parseFromString(rawHtml, 'text/html');
                doc.querySelectorAll('script, link, meta[http-equiv], object, embed, iframe, frame, frameset, base').forEach((node) => node.remove());
                doc.querySelectorAll('style').forEach((node) => {
                    const cleanedCss = _sanitizeCssText(node.textContent);
                    if (cleanedCss.trim()) {
                        node.textContent = cleanedCss;
                    } else {
                        node.remove();
                    }
                });
                doc.querySelectorAll('*').forEach((node) => {
                    Array.from(node.attributes || []).forEach((attr) => {
                        const name = String(attr.name || '').toLowerCase();
                        const value = String(attr.value || '').trim();
                        if (!name) return;
                        if (name.startsWith('on')) {
                            node.removeAttribute(attr.name);
                            return;
                        }
                        if (name === 'style') {
                            const cleanedStyle = _sanitizeCssText(value);
                            if (cleanedStyle.trim()) {
                                node.setAttribute(attr.name, cleanedStyle);
                            } else {
                                node.removeAttribute(attr.name);
                            }
                            return;
                        }
                        if (name === 'srcset') {
                            node.removeAttribute(attr.name);
                            return;
                        }
                        if (['href', 'src', 'poster', 'background', 'action', 'formaction', 'xlink:href'].includes(name)) {
                            if (/^(?:javascript:|cid:)/i.test(value)) {
                                node.removeAttribute(attr.name);
                            }
                        }
                    });
                });
                if (doc.head && !doc.head.querySelector('style[data-email-safe-base]')) {
                    const baseStyle = doc.createElement('style');
                    baseStyle.setAttribute('data-email-safe-base', '1');
                    baseStyle.textContent = 'html,body{max-width:100%;overflow-wrap:anywhere;}body{font-family:Segoe UI,Arial,sans-serif;}';
                    doc.head.prepend(baseStyle);
                }
                return doc.documentElement ? doc.documentElement.outerHTML : rawHtml;
            } catch (_) {
                return rawHtml
                    .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
                    .replace(/<script\b[^>]*\/?>/gi, '')
                    .replace(/\son[a-z]+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, '')
                    .replace(/\s(?:href|src)\s*=\s*(["'])\s*javascript:[\s\S]*?\1/gi, ' $1#$1')
                    .replace(/\s(?:href|src)\s*=\s*javascript:[^\s>]+/gi, '="#"')
                    .replace(/\s(?:href|src|poster|background)\s*=\s*(["'])\s*cid:[\s\S]*?\1/gi, ' $1#$1')
                    .replace(/url\(\s*(['"]?)(?!data:|#)[^)]*\1\s*\)/gi, "url('')")
                    .replace(/@import[\s\S]*?;/gi, '')
                    .replace(/@font-face\s*{[\s\S]*?}/gi, '')
                    .replace(/<meta\b[^>]*>/gi, '')
                    .replace(/<link\b[^>]*>/gi, '')
                    .replace(/<(object|embed|iframe)\b[^>]*>[\s\S]*?<\/\1>/gi, '');
            }
        }

        // Meta table (From/To/Date etc.)
        if (Object.keys(headers).length > 0) {
            const meta = document.createElement('div');
            meta.className = 'email-view-meta';
            for (const key of displayOrder) {
                const val = headers[key];
                if (!val) continue;
                const row = document.createElement('div');
                row.className = 'email-view-meta-row';
                const label = document.createElement('span');
                label.className = 'email-view-meta-label';
                label.textContent = key === 'cc' ? 'CC' : key.charAt(0).toUpperCase() + key.slice(1);
                const value = document.createElement('span');
                value.className = 'email-view-meta-value';
                value.textContent = val;
                row.appendChild(label);
                row.appendChild(value);
                meta.appendChild(row);
            }
            wrap.appendChild(meta);
        }

        // Divider
        const divider = document.createElement('hr');
        divider.className = 'email-view-divider';
        wrap.appendChild(divider);

        // Body — prefer sandboxed iframe with actual HTML; fall back to rendered markdown
        const fallbackMessageId = _emailReaderModal?.dataset?.messageId || '';
        const fallbackProvider = (_emailReaderModal?.dataset?.provider || '').toLowerCase();
        const messageId = headers['message-id'] || fallbackMessageId;
        const provider = (headers['provider'] || fallbackProvider || '').toLowerCase();

        if (messageId && (provider === 'outlook' || provider === 'gmail')) {
            const iframeWrap = document.createElement('div');
            iframeWrap.className = 'email-view-iframe-wrap';
            const loadingMsg = document.createElement('div');
            loadingMsg.className = 'email-view-iframe-loading';
            loadingMsg.textContent = 'Rendering email…';
            iframeWrap.appendChild(loadingMsg);

            const iframe = document.createElement('iframe');
            iframe.className = 'email-view-iframe';
            iframe.setAttribute('sandbox', 'allow-same-origin');
            iframe.setAttribute('referrerpolicy', 'no-referrer');
            iframe.style.display = 'none';
            iframeWrap.appendChild(iframe);
            wrap.appendChild(iframeWrap);

            const encodedId = encodeURIComponent(messageId);
            authFetch(`/api/email-html?id=${encodedId}&source=${encodeURIComponent(provider)}`)
                .then(r => r.text())
                .then(html => {
                    loadingMsg.remove();
                    iframe.srcdoc = _sanitizeEmailHtmlForSrcdoc(html);
                    iframe.style.display = '';
                    // Auto-resize after content loads
                    iframe.onload = () => {
                        try {
                            const h = iframe.contentDocument.documentElement.scrollHeight;
                            iframe.style.height = Math.max(200, h) + 'px';
                        } catch (_) {}
                    };
                })
                .catch(() => {
                    loadingMsg.textContent = 'Could not load email content.';
                });
        } else {
            // Fallback: render plain-text body as markdown
            const bodyDiv = document.createElement('div');
            bodyDiv.className = 'email-view-body';
            if (bodyText) {
                bodyDiv.innerHTML = _renderChatMarkdown(bodyText);
            }
            wrap.appendChild(bodyDiv);
        }

        return wrap;
    }

    function _resolveEmailInlineReply(text) {
        if (!_emailReaderModal) return false;
        const body = _emailReaderModal.querySelector('.email-reader-modal-body');
        if (!body) return false;

        // Replace loading with rendered email view
        body.innerHTML = '';
        const emailView = _renderEmailView(text);
        body.appendChild(emailView);

        // Store message-id and provider for reply/draft actions
        const _msgIdMatch = text.match(/^Message(?:-|\s)ID:\s*(.+)$/im);
        const _providerMatch = text.match(/^Provider:\s*(.+)$/im);
        if (_msgIdMatch) _emailReaderModal.dataset.messageId = _msgIdMatch[1].trim();
        if (_providerMatch) _emailReaderModal.dataset.provider = _providerMatch[1].trim().toLowerCase();
        _pendingInlineEmailRequest = null;

        // Add footer with action buttons now that content is loaded
        const box = _emailReaderModal.querySelector('.email-reader-modal-box');
        if (box && !box.querySelector('.email-reader-modal-footer')) {
            const footer = document.createElement('div');
            footer.className = 'email-reader-modal-footer';

            const closeBtnFooter = document.createElement('button');
            closeBtnFooter.className = 'email-reader-modal-btn email-reader-modal-btn--secondary';
            closeBtnFooter.textContent = 'Close';
            closeBtnFooter.addEventListener('click', () => _closeEmailInlineReader());

            const emailIdx = _emailReaderModal.dataset.emailIdx || '';
            // Use stored subject from dataset (set when row was clicked)
            const storedSubject = _emailReaderModal.dataset.emailSubject || '';
            const titleEl = _emailReaderModal.querySelector('.email-reader-modal-title');
            const emailTitle = storedSubject ||
                (titleEl ? titleEl.textContent.replace(/^\ud83d\udce7\s*/, '') : `E-mail ${emailIdx}`);

            const proposeBtn = document.createElement('button');
            proposeBtn.className = 'email-reader-modal-btn email-reader-modal-btn--secondary';
            proposeBtn.innerHTML = '\u270f\ufe0f Propose reply';
            proposeBtn.addEventListener('click', () => _startEmailProposeReply(emailIdx, emailTitle));

            const followupBtn = document.createElement('button');
            followupBtn.className = 'email-reader-modal-btn email-reader-modal-btn--primary';
            followupBtn.innerHTML = '\ud83d\udcac Ask follow-up';
            followupBtn.addEventListener('click', () => _startEmailFollowup(emailIdx, emailTitle));

            footer.appendChild(closeBtnFooter);
            footer.appendChild(proposeBtn);
            footer.appendChild(followupBtn);
            box.appendChild(footer);
        }

        setStatus('Verbonden met server', 'success');
        return true;
    }

    function _startEmailFollowup(emailIdx, emailTitle) {
        // Preserve the session ID so the follow-up has the email context on the backend
        const sessionToKeep = _preferredConversationSessionId || currentSessionId;

        // Save the row BEFORE closing the modal (which nulls _pendingEmailReaderRow)
        _followupEmailRow = _pendingEmailReaderRow;

        _closeEmailInlineReader();

        // Detach the session from all existing run lanes so _findRunIdBySessionId
        // won't match them — prevents _setActiveRun from re-rendering old content.
        for (const [, run] of runs) {
            run._sessionId = null;
        }

        // Reset UI state to give a clean chat start
        _pendingPreRunChatBubbles = [];
        _currentHandlingRunId = '';
        activeRunId = '';
        _setViewedHistorySession('');
        _setChatSessionPill('');

        // Hide all existing run lanes — user gets a clean chat view
        for (const [, run] of runs) {
            if (run.lane) run.lane.style.display = 'none';
        }

        _clearChatThread(true);

        // Restore session for backend continuity (so the LLM still has email context)
        if (sessionToKeep) {
            currentSessionId = sessionToKeep;
            _preferredConversationSessionId = sessionToKeep;
            _persistSession(currentSessionId);
            _setChatSessionPill(sessionToKeep);
        }
        _freshChatRequested = false;

        // Show context bubble with email title + re-open button
        const label = emailTitle && emailTitle.trim() ? emailTitle.trim() : `Email ${emailIdx}`;
        _appendEmailContextBubble(label);
        _scrollChatToBottom();

        // Focus the composer so the user can type immediately
        if (goalInput) {
            goalInput.value = '';
            goalInput.focus();
            if (typeof _refreshComposerModeUi === 'function') _refreshComposerModeUi();
        }
    }

    function _startEmailProposeReply(emailIdx, emailTitle) {
        if (!_emailReaderModal) return;
        const label = emailTitle && emailTitle.trim() ? emailTitle.trim() : `Email ${emailIdx}`;
        const messageId = _emailReaderModal.dataset.messageId || '';
        const provider = (_emailReaderModal.dataset.provider || '').toLowerCase();

        if (!messageId || !provider) {
            showToast('error', 'Could not prepare this email for draft generation.', 5000);
            return;
        }

        _draftReplyPending = true;
        _draftReplyRetryCount = 0;
        _draftReplyRequest = {
            transport: 'http',
            messageId,
            provider,
            subject: label,
            tone: 'match',
            length: 'medium',
        };

        _renderDraftReplyCanvas('loading');
        setStatus('Generating draft…', 'running', true);
        _sendDraftReplyRequest();
    }

    function _resolveDraftReplyInPopup(rawText) {
        if (!_emailReaderModal) return;

        // Clean up the draft text: strip markdown headings/bold wrappers
        const cleaned = rawText
            .replace(/^\*{1,3}[^*]+\*{1,3}:?\s*/m, '')  // leading bold heading
            .replace(/^#{1,3}\s+[^\n]+\n?/m, '')          // leading # heading
            .trim();

        const rendered = _renderDraftReplyCanvas('ready', { draftText: cleaned });
        _renderDraftReplyFooter(rendered.textarea);
        setStatus('Draft ready', 'success');
        rendered.textarea?.focus();
    }

    function _appendEmailContextBubble(label) {
        const bubble = document.createElement('div');
        bubble.className = 'chat-bubble system';

        const avatar = document.createElement('div');
        avatar.className = 'chat-avatar';
        avatar.textContent = '\ud83d\udce7';

        const content = document.createElement('div');
        content.className = 'chat-bubble-content';

        const roleEl = document.createElement('div');
        roleEl.className = 'chat-bubble-role';
        roleEl.textContent = 'System';

        const textEl = document.createElement('div');
        textEl.className = 'chat-bubble-text';

        const row = document.createElement('div');
        row.className = 'email-context-bubble';

        const titleSpan = document.createElement('span');
        titleSpan.className = 'email-context-title';
        titleSpan.textContent = label;

        const reopenBtn = document.createElement('button');
        reopenBtn.className = 'email-context-reopen-btn';
        reopenBtn.title = 'Re-open email';
        reopenBtn.textContent = '+';
        reopenBtn.addEventListener('click', () => {
            if (_followupEmailRow) _openEmailInlineReader(_followupEmailRow);
        });

        row.appendChild(titleSpan);
        row.appendChild(reopenBtn);

        const hint = document.createElement('div');
        hint.className = 'email-context-hint';
        hint.textContent = 'Ask your follow-up question below.';

        textEl.appendChild(row);
        textEl.appendChild(hint);
        content.appendChild(roleEl);
        content.appendChild(textEl);
        bubble.appendChild(avatar);
        bubble.appendChild(content);
        chatThread.appendChild(bubble);
        _ensureChatThreadVisibleState();
        _compactChatThreadLayout();
        _scrollChatToBottom();
    }

    // ── Calendar inline reader ──────────────────────────────────────────────
    let _calendarReaderModal = null;
    let _pendingCalendarReaderRow = null;
    let _followupCalendarRow = null;

    function _buildCalendarDetailView({ title, start, end, location }) {
        const view = document.createElement('div');
        view.className = 'calendar-event-view';
        const fields = [
            { label: 'Titel', value: title },
            { label: 'Start', value: start },
            { label: 'Einde', value: end && end !== '-' ? end : null },
            { label: 'Locatie', value: location && location !== '-' ? location : null },
        ];
        for (const { label, value } of fields) {
            if (!value) continue;
            const row = document.createElement('div');
            row.className = 'email-view-meta-row';
            const lbl = document.createElement('span');
            lbl.className = 'email-view-meta-label';
            lbl.textContent = label;
            const val = document.createElement('span');
            val.className = 'email-view-meta-value';
            val.textContent = value;
            row.appendChild(lbl);
            row.appendChild(val);
            view.appendChild(row);
        }
        return view;
    }

    function _closeCalendarInlineReader() {
        if (_pendingCalendarReaderRow) {
            _pendingCalendarReaderRow.classList.remove('calendar-row-selected');
            _pendingCalendarReaderRow = null;
        }
        if (_calendarReaderModal && _calendarReaderModal.parentNode) {
            _calendarReaderModal.parentNode.removeChild(_calendarReaderModal);
        }
        _calendarReaderModal = null;
    }

    function _startCalendarFollowup(idx, calTitle) {
        const sessionToKeep = _preferredConversationSessionId || currentSessionId;
        _followupCalendarRow = _pendingCalendarReaderRow;
        _closeCalendarInlineReader();

        for (const [, run] of runs) { run._sessionId = null; }
        _pendingPreRunChatBubbles = [];
        _currentHandlingRunId = '';
        activeRunId = '';
        _setViewedHistorySession('');
        _setChatSessionPill('');
        for (const [, run] of runs) { if (run.lane) run.lane.style.display = 'none'; }
        _clearChatThread(true);

        if (sessionToKeep) {
            currentSessionId = sessionToKeep;
            _preferredConversationSessionId = sessionToKeep;
            _persistSession(currentSessionId);
            _setChatSessionPill(sessionToKeep);
        }
        _freshChatRequested = false;

        const label = calTitle && calTitle.trim() ? calTitle.trim() : `Afspraak ${idx}`;
        _appendCalendarContextBubble(label);
        _scrollChatToBottom();

        if (goalInput) {
            goalInput.value = '';
            goalInput.focus();
            if (typeof _refreshComposerModeUi === 'function') _refreshComposerModeUi();
        }
    }

    function _startCalendarEdit(data, box, body, footer, row, titleSpan) {
        const { eventId, provider, title, startIso, endIso, location } = data;
        body.innerHTML = '';
        const form = document.createElement('div');
        form.className = 'calendar-edit-form';

        // Convert ISO string (may contain TZ offset or Z) to YYYY-MM-DDTHH:MM for datetime-local input
        const toDatetimeLocal = (iso) => {
            if (!iso || iso === '-') return '';
            const s = iso.replace(/([+-]\d{2}:\d{2}|Z)$/, '');
            return s.slice(0, 16); // YYYY-MM-DDTHH:MM
        };

        const makeField = (label, key, value, type = 'text', placeholder = '') => {
            const wrap = document.createElement('div');
            wrap.className = 'calendar-edit-field';
            const lbl = document.createElement('label');
            lbl.textContent = label;
            const inp = document.createElement('input');
            inp.type = type;
            inp.className = 'calendar-edit-input';
            inp.value = (value && value !== '-') ? value : '';
            inp.dataset.key = key;
            if (placeholder) inp.placeholder = placeholder;
            wrap.appendChild(lbl);
            wrap.appendChild(inp);
            return wrap;
        };

        form.appendChild(makeField('Titel', 'summary', title, 'text', 'Naam van de afspraak'));
        form.appendChild(makeField('Start', 'start', toDatetimeLocal(startIso), 'datetime-local'));
        form.appendChild(makeField('Einde', 'end', toDatetimeLocal(endIso), 'datetime-local'));
        form.appendChild(makeField('Locatie', 'location', location, 'text', 'optioneel'));
        body.appendChild(form);

        footer.innerHTML = '';
        footer.style.display = '';

        const cancelBtn = document.createElement('button');
        cancelBtn.className = 'email-reader-modal-btn email-reader-modal-btn--secondary';
        cancelBtn.textContent = 'Annuleren';
        cancelBtn.addEventListener('click', () => _closeCalendarInlineReader());

        const saveBtn = document.createElement('button');
        saveBtn.className = 'email-reader-modal-btn email-reader-modal-btn--primary';
        saveBtn.textContent = 'Opslaan';
        saveBtn.addEventListener('click', async () => {
            const payload = { event_id: eventId, provider };
            for (const inp of form.querySelectorAll('input[data-key]')) {
                const v = inp.value.trim();
                if (v) payload[inp.dataset.key] = v;
            }
            saveBtn.disabled = true;
            saveBtn.textContent = 'Opslaan\u2026';
            try {
                const res = await authFetch('/api/calendar-update', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify(payload),
                });
                const d = await res.json();
                if (d.ok) {
                    // Update the table row in-place so the user sees the new values
                    if (row) {
                        const _fmtCal = (iso) => {
                            if (!iso) return '';
                            try {
                                const dt = new Date(iso.length <= 16 ? iso + ':00' : iso);
                                return new Intl.DateTimeFormat('nl-NL', { weekday: 'short', day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' }).format(dt);
                            } catch { return iso; }
                        };
                        const rowCells = Array.from(row.querySelectorAll('td'));
                        if (payload.summary && rowCells[1]) rowCells[1].textContent = payload.summary;
                        if (payload.start  && rowCells[2]) rowCells[2].textContent = _fmtCal(payload.start);
                        if (payload.end    && rowCells[3]) rowCells[3].textContent = _fmtCal(payload.end);
                        if (payload.location !== undefined && rowCells[4]) rowCells[4].textContent = payload.location || '-';
                        if (payload.start) row.dataset.startIso = payload.start.length === 16 ? payload.start + ':00' : payload.start;
                        if (payload.end)   row.dataset.endIso   = payload.end.length   === 16 ? payload.end   + ':00' : payload.end;
                        if (payload.summary && titleSpan) titleSpan.textContent = '\uD83D\uDCC5 ' + payload.summary;
                    }
                    _closeCalendarInlineReader();
                    setStatus('Afspraak bijgewerkt', 'success');
                } else {
                    alert('Fout bij bijwerken: ' + (d.message || 'Onbekende fout'));
                    saveBtn.disabled = false;
                    saveBtn.textContent = 'Opslaan';
                }
            } catch (err) {
                alert('Fout: ' + err.message);
                saveBtn.disabled = false;
                saveBtn.textContent = 'Opslaan';
            }
        });

        footer.appendChild(cancelBtn);
        footer.appendChild(saveBtn);
    }

    function _startCalendarDelete(eventId, provider, calTitle, box, body, footer) {
        body.innerHTML = '';
        const confirm = document.createElement('div');
        confirm.className = 'calendar-delete-confirm';
        const p = document.createElement('p');
        p.innerHTML = 'Weet je zeker dat je <strong>' + escapeHtml(calTitle) + '</strong> wilt verwijderen?';
        confirm.appendChild(p);
        body.appendChild(confirm);

        footer.innerHTML = '';
        footer.style.display = '';

        const cancelBtn = document.createElement('button');
        cancelBtn.className = 'email-reader-modal-btn email-reader-modal-btn--secondary';
        cancelBtn.textContent = 'Annuleren';
        cancelBtn.addEventListener('click', () => _closeCalendarInlineReader());

        const delBtn = document.createElement('button');
        delBtn.className = 'email-reader-modal-btn email-reader-modal-btn--danger';
        delBtn.textContent = 'Verwijderen';
        delBtn.addEventListener('click', async () => {
            delBtn.disabled = true;
            delBtn.textContent = 'Verwijderen\u2026';
            try {
                const res = await authFetch('/api/calendar-delete', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ event_id: eventId, provider }),
                });
                const d = await res.json();
                if (d.ok) {
                    _closeCalendarInlineReader();
                    setStatus('Afspraak verwijderd', 'success');
                } else {
                    alert('Kon niet verwijderen: ' + (d.message || 'Onbekende fout'));
                    delBtn.disabled = false;
                    delBtn.textContent = 'Verwijderen';
                }
            } catch (err) {
                alert('Fout: ' + err.message);
                delBtn.disabled = false;
                delBtn.textContent = 'Verwijderen';
            }
        });

        footer.appendChild(cancelBtn);
        footer.appendChild(delBtn);
    }

    function _openCalendarInlineReader(row) {
        if (!row) return;
        _closeCalendarInlineReader();

        row.classList.add('calendar-row-selected');
        _pendingCalendarReaderRow = row;

        const idx = row.dataset.calendarIndex || '';
        const eventId = row.dataset.eventId || '';
        const provider = row.dataset.provider || '';
        const startIso = row.dataset.startIso || '';
        const endIso = row.dataset.endIso || '';

        // Extract visible cell values (# | Titel | Start | Einde | Locatie)
        const cells = Array.from(row.querySelectorAll('td'));
        const title    = cells[1] ? cells[1].textContent.trim() : '';
        const start    = cells[2] ? cells[2].textContent.trim() : '';
        const end      = cells[3] ? cells[3].textContent.trim() : '';
        const location = cells[4] ? cells[4].textContent.trim() : '';

        // Build modal (reuse email-reader-modal CSS)
        const overlay = document.createElement('div');
        overlay.className = 'email-reader-modal';
        overlay.addEventListener('click', e => { if (e.target === overlay) _closeCalendarInlineReader(); });

        const box = document.createElement('div');
        box.className = 'email-reader-modal-box';

        const header = document.createElement('div');
        header.className = 'email-reader-modal-header';
        const titleSpan = document.createElement('span');
        titleSpan.className = 'email-reader-modal-title';
        titleSpan.textContent = '\uD83D\uDCC5 ' + (title || `Afspraak ${idx}`);
        const closeBtn = document.createElement('button');
        closeBtn.className = 'email-reader-modal-close';
        closeBtn.title = 'Sluiten (Esc)';
        closeBtn.textContent = '\u2715';
        closeBtn.addEventListener('click', () => _closeCalendarInlineReader());
        header.appendChild(titleSpan);
        header.appendChild(closeBtn);

        const body = document.createElement('div');
        body.className = 'email-reader-modal-body';
        body.appendChild(_buildCalendarDetailView({ title, start, end, location }));

        const footer = document.createElement('div');
        footer.className = 'email-reader-modal-footer';

        const closeBtnFooter = document.createElement('button');
        closeBtnFooter.className = 'email-reader-modal-btn email-reader-modal-btn--secondary';
        closeBtnFooter.textContent = 'Sluiten';
        closeBtnFooter.addEventListener('click', () => _closeCalendarInlineReader());

        const followupBtn = document.createElement('button');
        followupBtn.className = 'email-reader-modal-btn email-reader-modal-btn--secondary';
        followupBtn.innerHTML = '\uD83D\uDCAC Vraag follow-up';
        followupBtn.addEventListener('click', () => _startCalendarFollowup(idx, title));

        footer.appendChild(closeBtnFooter);

        // Only show edit/delete when we have an event_id
        if (eventId) {
            const editBtn = document.createElement('button');
            editBtn.className = 'email-reader-modal-btn email-reader-modal-btn--secondary';
            editBtn.innerHTML = '\u270F\uFE0F Bewerken';
            editBtn.addEventListener('click', () =>
                _startCalendarEdit({ idx, eventId, provider, title, start, end, startIso, endIso, location }, box, body, footer, row, titleSpan));

            const delBtn = document.createElement('button');
            delBtn.className = 'email-reader-modal-btn email-reader-modal-btn--danger';
            delBtn.innerHTML = '\uD83D\uDDD1\uFE0F Verwijderen';
            delBtn.addEventListener('click', () =>
                _startCalendarDelete(eventId, provider, title, box, body, footer));

            footer.appendChild(editBtn);
            footer.appendChild(delBtn);
        }

        footer.appendChild(followupBtn);

        box.appendChild(header);
        box.appendChild(body);
        box.appendChild(footer);
        overlay.appendChild(box);
        document.body.appendChild(overlay);
        _calendarReaderModal = overlay;

        const escHandler = e => {
            if (e.key === 'Escape') {
                _closeCalendarInlineReader();
                document.removeEventListener('keydown', escHandler);
            }
        };
        document.addEventListener('keydown', escHandler);
    }

    function _appendCalendarContextBubble(label) {
        const bubble = document.createElement('div');
        bubble.className = 'chat-bubble system';
        const avatar = document.createElement('div');
        avatar.className = 'chat-avatar';
        avatar.textContent = '\uD83D\uDCC5';
        const content = document.createElement('div');
        content.className = 'chat-bubble-content';
        const roleEl = document.createElement('div');
        roleEl.className = 'chat-bubble-role';
        roleEl.textContent = 'Agenda';
        const textEl = document.createElement('div');
        textEl.className = 'chat-bubble-text';
        const row = document.createElement('div');
        row.className = 'email-context-bubble';
        const titleSpan = document.createElement('span');
        titleSpan.className = 'email-context-title';
        titleSpan.textContent = label;
        const reopenBtn = document.createElement('button');
        reopenBtn.className = 'email-context-reopen-btn';
        reopenBtn.title = 'Afspraak opnieuw openen';
        reopenBtn.textContent = '+';
        reopenBtn.addEventListener('click', () => {
            if (_followupCalendarRow) _openCalendarInlineReader(_followupCalendarRow);
        });
        row.appendChild(titleSpan);
        row.appendChild(reopenBtn);
        const hint = document.createElement('div');
        hint.className = 'email-context-hint';
        hint.textContent = 'Stel je vervolgvraag hieronder.';
        textEl.appendChild(row);
        textEl.appendChild(hint);
        content.appendChild(roleEl);
        content.appendChild(textEl);
        bubble.appendChild(avatar);
        bubble.appendChild(content);
        chatThread.appendChild(bubble);
        _ensureChatThreadVisibleState();
        _compactChatThreadLayout();
        _scrollChatToBottom();
    }

    // ── Suggestion chip click handler (event delegation) ───────────────────
    if (chatThread) {
        chatThread.addEventListener('click', (e) => {
            const chip = e.target.closest('.chat-empty-chip');
            if (chip && goalInput) {
                goalInput.value = chip.dataset.query || chip.textContent.trim();
                goalInput.focus();
                if (typeof _refreshComposerModeUi === 'function') _refreshComposerModeUi();
            }

            // ── Email table row click → popup modal ───────────────────────
            const row = e.target.closest('tr[data-email-index]');
            if (row) {
                // Toggle: clicking the already-selected row closes the reader
                if (row === _pendingEmailReaderRow) {
                    _closeEmailInlineReader();
                } else {
                    _openEmailInlineReader(row);
                }
            }

            // ── Calendar table row click → popup modal ─────────────────────
            const calRow = e.target.closest('tr[data-calendar-index]');
            if (calRow) {
                if (calRow === _pendingCalendarReaderRow) {
                    _closeCalendarInlineReader();
                } else {
                    _closeCalendarInlineReader();
                    _openCalendarInlineReader(calRow);
                }
            }
        });
    }

    function _resetSchedulePicker() {
        _schedulePickerMode = 'once';
        if (scheduleFirstRunInput) scheduleFirstRunInput.value = '';
        _updateSchedulePickerUI();
        _toggleScheduleDropdown(false);
    }

    if (copilotRunNowBtn) copilotRunNowBtn.addEventListener('click', () => _runCopilotById('morning-brief', 'settings'));

    async function executeComposerAction(forcedMode = '') {
        const parsedInput = _parseComposerInput(goalInput.value);
        const mode = (forcedMode || parsedInput.mode || 'run');
        const goal  = parsedInput.text;
        const model = appSettings.main_model || '';
        const chatModel = appSettings.fast_model || appSettings.main_model || '';
        let context = contextInput.value.trim();

        if (_activeLlmRunCount() >= 3) {
            setStatus('Maximum of 3 concurrent agent workflows already running', 'error');
            setTimeout(() => setStatus('Connected to server', 'success'), 3000);
            return;
        }

        if (_modelsLoading && availableModels.length === 0) {
            showToast('warning', 'Still spinning up — try again in a few seconds.', 4000);
            return;
        }

        if (availableModels.length === 0) {
            alert('No models available. Open Settings and ensure at least one model is available.');
            return;
        }

        if (!goal || !model) {
            alert('Please select a main model in Settings and enter a goal.');
            return;
        }

        const _hasPendingBootstrapChat = _pendingSessionBootstrap && !currentSessionId && !activeRunId && runs.size === 0;
        const _hasBootstrapAttachments = (!!fileInput && fileInput.files.length > 0)
            || !!(folderPathInput && folderPathInput.value.trim())
            || _cloudAttachedFiles.length > 0;
        if (_hasPendingBootstrapChat && !_hasBootstrapAttachments) {
            _queuedBootstrapFollowUps.push({
                goal,
                model: chatModel,
                context,
            });
            appendLog(goal, 'info', 'You', true);
            _appendChatBubble('user', goal);
            _showThinkingBubble();
            _forceStickChatBottom(6000);
            setStatus('Waiting for the current chat session to start…', 'running', true);
            if (goalInput) {
                goalInput.value = '';
                _refreshComposerModeUi();
            }
            return;
        }

        // ── Follow-up: if a session already exists, continue as chat instead of new run ──
        // Use the active run session, then fall back to persisted global session.
        // Viewing a history session (read-only) does NOT count as continuation intent.
        const _activeRunSession = (activeRunId && runs.has(activeRunId) && !String(activeRunId).startsWith('hist_'))
            ? _resolveSessionForRun(activeRunId)
            : null;
        const _preferredSession = _freshChatRequested ? '' : _composerPreferredSessionId();
        const _resolvedSession = _freshChatRequested ? '' : (_preferredSession || _activeRunSession || '');
        const _hasExistingSession = !!_resolvedSession;
        if (_hasExistingSession) {
            // Re-route as a follow-up chat on the existing session
            const targetSession = _resolvedSession;
            let targetRunId = _findRunIdBySessionId(targetSession, false);
            let hydratedSessionLane = false;
            if (targetRunId && !runs.has(targetRunId)) {
                targetRunId = _ensureChatLane(targetSession);
                hydratedSessionLane = true;
            }
            if (!targetRunId && activeRunId && runs.has(activeRunId)) {
                const activeRunSession = String(runs.get(activeRunId)?._sessionId || '').trim();
                if (activeRunSession === String(targetSession).trim()) {
                    targetRunId = activeRunId;
                }
            }
            if (!targetRunId) {
                const seed = targetSession || `temp_${Date.now().toString(36)}`;
                targetRunId = _ensureChatLane(seed);
                hydratedSessionLane = true;
            } else {
                _setActiveRun(targetRunId);
            }

            if (hydratedSessionLane) {
                const hydratedRun = targetRunId && runs.has(targetRunId) ? runs.get(targetRunId) : null;
                if (hydratedRun && (!Array.isArray(hydratedRun._chatBubbles) || hydratedRun._chatBubbles.length === 0)) {
                    try {
                        const session = await _fetchSession(targetSession);
                        _renderSessionChatHistory(session);
                    } catch (err) {
                        console.warn('Could not hydrate session into follow-up lane:', err);
                    }
                }
            }

            let chatContext = contextInput.value.trim();
            const folderPath = folderPathInput ? folderPathInput.value.trim() : '';
            const _pendingLocalFiles = Array.from(fileInput?.files || []).map((f) => String(f?.name || '').trim()).filter(Boolean);
            const _pendingCloudFiles = _cloudAttachedFiles.map((cf) => {
                const src = cf.provider === 'google_drive' ? 'Google Drive' : 'OneDrive';
                return `${cf.name} (${src})`;
            });
            const _attachNames = [];
            if (fileInput && fileInput.files.length > 0) {
                for (const f of fileInput.files) _attachNames.push(f.name);
            }
            if (folderPath) _attachNames.push('📁 ' + (folderPath.split(/[\\/]/).pop() || folderPath));
            if (_cloudAttachedFiles.length > 0) {
                for (const cf of _cloudAttachedFiles) _attachNames.push('☁ ' + cf.name);
            }
            if (fileInput.files.length > 0 || folderPath || _cloudAttachedFiles.length > 0) {
                try {
                    for (const file of fileInput.files) {
                        const isPdf   = file.name.toLowerCase().endsWith('.pdf');
                        const isImage = /\.(jpe?g|png|webp|gif)$/i.test(file.name);
                        const isMedia = /\.(mp3|wav|m4a|ogg|flac|mp4|mov|webm|mkv)$/i.test(file.name);
                        if (isPdf || isImage || isMedia) {
                            const fd = new FormData();
                            fd.append('file', file);
                            const res  = await authFetch(`${BACKEND_URL}/api/upload`, { method: 'POST', body: fd });
                            const data = await res.json();
                            chatContext += `\n\n--- ${file.name} ---\n${data.extracted_text || ''}`;
                        } else {
                            const text = await file.text();
                            chatContext += `\n\n--- ${file.name} ---\n${text}`;
                        }
                    }
                    if (folderPath) {
                        const res  = await authFetch(`${BACKEND_URL}/api/folder-index?path=${encodeURIComponent(folderPath)}`);
                        const data = await res.json();
                        if (!data.error) {
                            chatContext += `\n\n${_buildFolderAttachmentContext(data)}`;
                        }
                    }
                    if (_cloudAttachedFiles.length > 0) {
                        for (const cf of _cloudAttachedFiles) {
                            try {
                                const res = await authFetch(`${BACKEND_URL}/api/cloud-read?provider=${encodeURIComponent(cf.provider)}&file_id=${encodeURIComponent(cf.id)}`);
                                const data = await res.json();
                                if (data.text) {
                                    const provLabel = cf.provider === 'google_drive' ? 'Google Drive' : 'OneDrive';
                                    chatContext += `\n\n--- ${cf.name} (${provLabel}) ---\n${data.text}`;
                                }
                            } catch (e) { console.warn(`Cloud read failed for ${cf.name}:`, e); }
                        }
                        _cloudAttachedFiles.length = 0;
                    }
                } catch (e) { console.warn('Follow-up attachment processing failed:', e); }
                if (fileInput) { fileInput.value = ''; fileLabel.textContent = 'Attach files'; }
                if (folderPathInput) { folderPathInput.value = ''; }
                if (folderClearBtn) folderClearBtn.style.display = 'none';
                _refreshAttachChips();
            }

            _currentHandlingRunId = targetRunId;
            const _attachMeta = _attachNames.length ? _attachNames.join(', ') : '';
            _storeLastSentContextSnapshot({
                sessionId: targetSession,
                typedContextChars: String(contextInput?.value || '').trim().length,
                localFiles: _pendingLocalFiles,
                folderPath,
                cloudFiles: _pendingCloudFiles,
                structuralContext: _captureStructuralContextSnapshot(appSettings),
                effectiveContextChars: String(chatContext || '').trim().length,
            });
            const _isCompactCmd = goal.trim().toLowerCase() === '/compact';
            appendLog(goal, 'info', 'You', true);
            if (_isCompactCmd) {
                _appendChatBubble('system', 'Compacting session history\u2026');
            } else {
                _appendChatBubble('user', goal, _attachMeta);
            }
            _showThinkingBubble();
            _forceStickChatBottom(6000);
            setStatus(_isCompactCmd ? 'Compacting\u2026' : 'Chatting\u2026', 'running', true);

            const _sendFollowUp = () => {
                ws.send(JSON.stringify({
                    action: 'chat',
                    session_id: targetSession,
                    run_id: targetRunId,
                    model: appSettings.fast_model || appSettings.main_model || '',
                    message: goal,
                    context: chatContext || '',
                }));
                if (goalInput) { goalInput.value = ''; _refreshComposerModeUi(); }
            };

            if (!ws || ws.readyState !== WebSocket.OPEN) {
                if (_reconnectTimer) { clearTimeout(_reconnectTimer); _reconnectTimer = null; }
                connectWebSocket();
                setTimeout(() => {
                    ws.readyState === WebSocket.OPEN
                        ? _sendFollowUp()
                        : alert('Could not connect to the server. Make sure the server is running.');
                }, 800);
            } else {
                _sendFollowUp();
            }
            return;
        }

        // ── New agent workflow (no existing session) ──
        currentSessionId = null;
        _preferredConversationSessionId = '';
        _pendingSessionBootstrap = true;
        _setChatSessionPill('');
        _clearChatThread(false);

        const folderPath = folderPathInput ? folderPathInput.value.trim() : ''
        const _wfAttachNames = [];
        if (fileInput && fileInput.files.length > 0) {
            for (const f of fileInput.files) _wfAttachNames.push(f.name);
        }
        if (folderPath) _wfAttachNames.push('📁 ' + (folderPath.split(/[\\/]/).pop() || folderPath));
        if (_cloudAttachedFiles.length > 0) {
            for (const cf of _cloudAttachedFiles) _wfAttachNames.push('☁ ' + cf.name);
        }
        const _wfPendingLocalFiles = Array.from(fileInput?.files || []).map((f) => String(f?.name || '').trim()).filter(Boolean);
        const _wfPendingCloudFiles = _cloudAttachedFiles.map((cf) => {
            const src = cf.provider === 'google_drive' ? 'Google Drive' : 'OneDrive';
            return `${cf.name} (${src})`;
        });
        const _wfAttachMeta = _wfAttachNames.length ? _wfAttachNames.join(', ') : '';
        _appendChatBubble('user', goal, _wfAttachMeta);
        _showThinkingBubble();
        _forceStickChatBottom(6000);
        if (fileInput.files.length > 0 || folderPath || _cloudAttachedFiles.length > 0) {
            const uploadStatus = document.getElementById('upload-status');
            const folderStatus = document.getElementById('folder-path-status');
            uploadStatus.style.display = 'flex';
            uploadStatus.className = 'upload-status uploading';
            uploadStatus.textContent = `Processing\u2026`;
            try {
                // Loose files — read content eagerly into context (PDF/image via backend)
                for (const file of fileInput.files) {
                    const isPdf   = file.name.toLowerCase().endsWith('.pdf');
                    const isImage = /\.(jpe?g|png|webp|gif)$/i.test(file.name);
                    const isMedia = /\.(mp3|wav|m4a|ogg|flac|mp4|mov|webm|mkv)$/i.test(file.name);
                    if (isPdf || isImage || isMedia) {
                        const fd = new FormData();
                        fd.append('file', file);
                        const res  = await authFetch(`${BACKEND_URL}/api/upload`, { method: 'POST', body: fd });
                        const data = await res.json();
                        context += `\n\n--- ${file.name} ---\n${data.extracted_text || ''}`;
                    } else {
                        const text = await file.text();
                        context += `\n\n--- ${file.name} ---\n${text}`;
                    }
                }

                // Folder — fetch file tree from server, inject as permission + index
                let folderFileCount = 0;
                if (folderPath) {
                    uploadStatus.textContent = `Indexing folder\u2026`;
                    const res  = await authFetch(`${BACKEND_URL}/api/folder-index?path=${encodeURIComponent(folderPath)}`);
                    const data = await res.json();
                    if (data.error) {
                        if (folderStatus) { folderStatus.textContent = `\u26A0 ${data.error}`; folderStatus.className = 'folder-path-status err'; }
                        uploadStatus.style.display = 'none';
                        return;
                    }
                    folderFileCount = data.file_count;
                    context += `\n\n${_buildFolderAttachmentContext(data)}`;
                    if (folderStatus) { folderStatus.textContent = ''; folderStatus.className = 'folder-path-status'; }
                }

                const parts = [];
                if (fileInput.files.length) parts.push(`${fileInput.files.length} file(s) attached`);
                if (folderPath) parts.push(`folder indexed (${folderFileCount} files available)`);
                // Cloud files — read content from Google Drive / OneDrive
                if (_cloudAttachedFiles.length > 0) {
                    uploadStatus.textContent = `Reading cloud files\u2026`;
                    for (const cf of _cloudAttachedFiles) {
                        try {
                            const res = await authFetch(`${BACKEND_URL}/api/cloud-read?provider=${encodeURIComponent(cf.provider)}&file_id=${encodeURIComponent(cf.id)}`);
                            const data = await res.json();
                            if (data.text) {
                                const provLabel = cf.provider === 'google_drive' ? 'Google Drive' : 'OneDrive';
                                context += `\n\n--- ${cf.name} (${provLabel}) ---\n${data.text}`;
                            }
                        } catch (e) {
                            console.warn(`Cloud read failed for ${cf.name}:`, e);
                        }
                    }
                    parts.push(`${_cloudAttachedFiles.length} cloud file(s) attached`);
                    _cloudAttachedFiles.length = 0;
                }
                uploadStatus.className = 'upload-status done';
                uploadStatus.textContent = (parts.join(', ') || 'Done') + '.';
            } catch (e) {
                uploadStatus.className = 'upload-status err';
                uploadStatus.textContent = `Error: ${e.message}`;
                return;
            }
        }

        _storeLastSentContextSnapshot({
            sessionId: '',
            typedContextChars: String(contextInput?.value || '').trim().length,
            localFiles: _wfPendingLocalFiles,
            folderPath,
            cloudFiles: _wfPendingCloudFiles,
            structuralContext: _captureStructuralContextSnapshot(appSettings),
            effectiveContextChars: String(context || '').trim().length,
        });

        memoryContainer.innerHTML = '';
        if (memoryCount) memoryCount.textContent   = '';
        recalledMemoryRaw = '';

        if (!ws || ws.readyState !== WebSocket.OPEN) {
            // Cancel any pending reconnect timer and connect immediately
            if (_reconnectTimer) {
                clearTimeout(_reconnectTimer);
                _reconnectTimer = null;
            }
            connectWebSocket();
            setTimeout(() => {
                ws.readyState === WebSocket.OPEN
                    ? sendStart(goal, model, context)
                    : alert('Could not connect to the server. Make sure the server is running.');
            }, 800);
        } else {
            sendStart(goal, model, context);
        }

        // If schedule mode is repeat, create a recurring schedule
        if (_schedulePickerMode === 'repeat') {
            const interval = scheduleIntervalInput ? (Number(scheduleIntervalInput.value) || 30) : 30;
            const unit = scheduleUnit ? scheduleUnit.value : 'minutes';
            const firstRun = scheduleFirstRunInput ? scheduleFirstRunInput.value : '';
            addSchedule(goal, model, context, interval, unit, true, firstRun);
            // Reset composer schedule to "once" for next use
            _resetSchedulePicker();
        }

        if (goalInput) {
            goalInput.value = '';
            _refreshComposerModeUi();
        }
        // Clear attachments after submission
        if (fileInput) { fileInput.value = ''; fileLabel.textContent = 'Attach files'; }
        if (folderPathInput) { folderPathInput.value = ''; }
        if (folderClearBtn) folderClearBtn.style.display = 'none';
        _refreshAttachChips();
    }

    // --- Start / Stop ---
    if (startBtn) startBtn.addEventListener('click', async () => {
        executeComposerAction();
    });

    function sendStart(goal, model, context) {
        const use_internet     = internetToggle ? internetToggle.checked : false;
        const require_approval = approvalToggle  ? approvalToggle.checked  : false;
        const speed_mode       = speedToggle ? speedToggle.checked : true;
        const fast_model       = appSettings.fast_model || '';
        const use_outlook      = !!(emailToggle && emailToggle.checked && !emailToggle.disabled);
        if (tokenCount) tokenCount.textContent = '0';
        if (budgetCount) budgetCount.textContent = '0 st · 0 tl';
        if (tokenCounterEl) tokenCounterEl.style.display = 'none';
        if (budgetCounterEl) budgetCounterEl.style.display = 'none';
        setStatus('Starting\u2026', 'running', true);
        ws.send(JSON.stringify({ action: 'start', goal, model, context, use_internet, require_approval, speed_mode, fast_model, use_outlook }));
    }

                return {
                    runCopilotById: _runCopilotById,
                    runSelectedCopilotQuickstart: _runSelectedCopilotQuickstart,
                    loadSchedules: _loadSchedules,
                    formatInterval: _formatInterval,
                    saveSchedules: _saveSchedules,
                    addSchedule,
                    removeSchedule,
                    toggleSchedule,
                    executeScheduledRun: _executeScheduledRun,
                    schedulePollingTick: _schedulePollingTick,
                    updateScheduleCountdowns: _updateScheduleCountdowns,
                    startSchedulePolling: _startSchedulePolling,
                    stopSchedulePolling: _stopSchedulePolling,
                    initScheduleTimers: _initScheduleTimers,
                    renderSchedules,
                    showSchedulePopover: _showSchedulePopover,
                    updateSchedulePickerUI: _updateSchedulePickerUI,
                    toggleScheduleDropdown: _toggleScheduleDropdown,
                    resetDraftReplyState: _resetDraftReplyState,
                    sendDraftReplyRequest: _sendDraftReplyRequest,
                    retryDraftReplyNow: _retryDraftReplyNow,
                    showDraftReplyRetryState: _showDraftReplyRetryState,
                    consumePendingInlineEmailReply: _consumePendingInlineEmailReply,
                    resolveEmailInlineReply: _resolveEmailInlineReply,
                    openEmailInlineReader: _openEmailInlineReader,
                    closeEmailInlineReader: _closeEmailInlineReader,
                    openCalendarInlineReader: _openCalendarInlineReader,
                    closeCalendarInlineReader: _closeCalendarInlineReader,
                    executeComposerAction,
                    sendStart
                };
            }
        })();
    };
})();